Rust Cookbook 中文版
中文译注(Chinese translation of the Rust Cookbook):
- 《Rust Cookbook 中文版》 翻译自 The Rust Cookbook(Cookin' with Rust),查看此书的 Github 翻译项目。
- 本书翻译内容源自 zzy的开源的翻译版本,rust-lang-cn 开源组织对原译者感激不尽!
- 许可协议:跟随原翻译版本的 MIT 和 Apache 2.0 双许可授权。另本站的英文版保持原有的 CC0 协议。
- 本站支持文档中英文切换,点击页面右上角语言图标可切换到相同章节的英文页面,英文版每天都会自动同步一次官方的最新版本。
- 若发现本页表达错误或帮助我们改进翻译,可点击右上角的编辑按钮打开本页对应源码文件进行编辑和修改,Rust 中文资源的开源组织发展离不开大家,感谢您的支持和帮助!
《Rust Cookbook 中文版》是 Rust 程序设计语言(Rust 官方教程简体中文版)的简要实例示例集合:展示了在 Rust 生态系统中,使用各类 crate 来完成常见编程任务的良好实践。
了解更多关于《Rust Cookbook 中文版》一书的信息,请阅读关于本书,包括:如何阅读本书的提示、如何使用实例示例,以及关于注释的约定。
参与贡献
Rust Cookbook 项目(英文和中文两者)的目的是让 Rust 程序员新手能够更容易地参与到 Rust 社区中,因此欢迎你做出贡献,详情可查看“参与贡献”文件。
算法
生成随机值
实例名称 | Crates | 类别 |
---|---|---|
生成随机数 | ||
生成范围内随机数 | ||
生成给定分布随机数 | ||
生成自定义类型随机值 | ||
从一组字母数字字符创建随机密码 | ||
从一组用户定义字符创建随机密码 |
Vector 排序
实例名称 | Crates | 类别 |
---|---|---|
整数 Vector 排序 | ||
浮点数 Vector 排序 | ||
结构体 Vector 排序 |
命令行
参数解析
实例名称 | Crates | 类别 |
---|---|---|
解析命令行参数 |
ANSI 终端
实例名称 | Crates | 类别 |
---|---|---|
ANSI 终端 |
压缩
使用 tar 包
实例名称 | Crates | 类别 |
---|---|---|
解压 tar 包 | ||
压缩目录为 tar 包 | ||
从路径移除前缀时,解压 tar 包 |
并发/并行
显式线程
实例名称 | Crates | 类别 |
---|---|---|
生成短期线程 | ||
创建并发的数据管道 | ||
在两个线程间传递数据 | ||
保持全局可变状态 | ||
对所有 iso 文件的 SHA256 值并发求和 | ||
将绘制分形的线程分派到线程池 |
数据并行
实例名称 | Crates | 类别 |
---|---|---|
并行改变数组中元素 | ||
并行测试集合中任意或所有的元素是否匹配给定断言 | ||
使用给定断言并行搜索项 | ||
对 vector 并行排序 | ||
Map-reduce 并行计算 | ||
并行生成 jpg 缩略图 |
密码学
散列(哈希)
实例名称 | Crates | 类别 |
---|---|---|
计算文件的 SHA-256 摘要 | ||
使用 HMAC 摘要对消息进行签名和验证 |
加密
实例名称 | Crates | 类别 |
---|---|---|
使用 PBKDF2 对密码进行加密(salt)和散列(hash)运算 |
数据结构
位域
实例名称 | Crates | 类别 |
---|---|---|
定义并操作位域风格的类型 |
数据库
SQLite
实例名称 | Crates | 类别 |
---|---|---|
创建 SQLite 数据库 | ||
数据插入和查询 | ||
事务处理 |
使用 Postgres
实例名称 | Crates | 类别 |
---|---|---|
Postgres 数据库中创建表 | ||
数据插入和查询 | ||
数据聚合 |
日期及时间
期间和计算
实例名称 | Crates | 类别 |
---|---|---|
测量运行时间 | ||
执行日期检查和时间计算 | ||
时间的时区转换 |
解析与显示
实例名称 | Crates | 类别 |
---|---|---|
检查日期和时间 | ||
日期和 UNIX 时间戳的互相转换 | ||
日期和时间的格式化显示 | ||
将字符串解析为 DateTime 结构体 |
开发工具
调试工具
日志信息
实例名称 | Crates | 类别 |
---|---|---|
记录调试信息到控制台 | ||
记录错误信息到控制台 | ||
记录信息时,用标准输出 stdout 替换标准错误 stderr | ||
使用自定义日志记录器记录信息 | ||
记录到 Unix 系统日志 |
日志配置
实例名称 | Crates | 类别 |
---|---|---|
启用每个模块的日志级别 | ||
用自定义环境变量设置日志记录 | ||
在日志信息中包含时间戳 | ||
将信息记录到自定义位置 |
版本控制
实例名称 | Crates | 类别 |
---|---|---|
解析并递增版本字符串 | ||
解析复杂的版本字符串 | ||
检查给定版本是否为预发布版本 | ||
查询适配给定范围的最新版本 | ||
检查外部命令的版本兼容性 |
构建时
实例名称 | Crates | 类别 |
---|---|---|
编译并静态链接到绑定的 C 语言库 | ||
编译并静态链接到绑定的 C++ 语言库 | ||
编译 C 语言库时自定义设置 |
编码
字符集
实例名称 | Crates | 类别 |
---|---|---|
百分比编码(URL 编码)字符串 | ||
将字符串编码为 application/x-www-form-urlencoded | ||
编码和解码十六进制 | ||
编码和解码 base64 |
CSV 处理
实例名称 | Crates | 类别 |
---|---|---|
读取 CSV 记录 | ||
读取有不同分隔符的 CSV 记录 | ||
筛选匹配断言的 CSV 记录 | ||
用 Serde 处理无效的 CSV 数据 | ||
将记录序列化为 CSV | ||
用 Serde 将记录序列化为 CSV | ||
转换 CSV 文件的列 |
结构化数据
实例名称 | Crates | 类别 |
---|---|---|
对非结构化 JSON 序列化和反序列化 | ||
反序列化 TOML 配置文件 | ||
以小端模式(低位模式)字节顺序读写整数 |
错误处理
处理错误变量
实例名称 | Crates | 类别 |
---|---|---|
在 main 方法中对错误适当处理 | ||
避免在错误转变过程中遗漏错误 | ||
获取复杂错误场景的回溯 |
文件系统
文件读写
实例名称 | Crates | 类别 |
---|---|---|
读取文件的字符串行 | ||
避免读取写入同一文件 | ||
使用内存映射随机访问文件 |
目录遍历
实例名称 | Crates | 类别 |
---|---|---|
过去 24 小时内修改过的文件名 | ||
查找给定路径的循环 | ||
递归查找重名文件 | ||
使用给定断言递归查找所有文件 | ||
跳过隐藏文件遍历目录 | ||
在给定深度的目录,递归计算文件大小 | ||
递归查找所有 png 文件 | ||
忽略文件名大小写,使用给定模式查找所有文件 |
硬件支持
处理器
实例名称 | Crates | 类别 |
---|---|---|
检查逻辑 cpu 内核的数量 |
内存管理
常量
实例名称 | Crates | 类别 |
---|---|---|
声明延迟计算常量 |
网络
服务器
实例名称 | Crates | 类别 |
---|---|---|
监听未使用的 TCP/IP 端口 |
操作系统
外部命令
实例名称 | Crates | 类别 |
---|---|---|
运行外部命令并处理 stdout | ||
运行传递 stdin 的外部命令,并检查错误代码 | ||
运行管道传输的外部命令 | ||
将子进程的 stdout 和 stderr 重定向到同一个文件 | ||
持续处理子进程的输出 | ||
读取环境变量 |
科学计算
数学
线性代数
实例名称 | Crates | 类别 |
---|---|---|
Vector 范数 | ||
Vector 比较 | ||
矩阵相加 | ||
矩阵相乘 | ||
标量、vector、矩阵相乘 | ||
矩阵求逆 | ||
(反)序列化矩阵 |
三角学
实例名称 | Crates | 类别 |
---|---|---|
计算三角形的边长 | ||
验证正切(tan)等于正弦(sin)除以余弦(cos) | ||
地球上两点之间的距离 |
复数
实例名称 | Crates | 类别 |
---|---|---|
创建复数 | ||
复数相加 | ||
复数的数学函数 |
统计学
实例名称 | Crates | 类别 |
---|---|---|
集中趋势度量 | ||
计算标准偏差 |
其它数学计算
实例名称 | Crates | 类别 |
---|---|---|
大数 |
文本处理
正则表达式
实例名称 | Crates | 类别 |
---|---|---|
验证并提取电子邮件登录信息 | ||
从文本提取标签元素唯一的列表 | ||
从文本提取电话号码 | ||
通过匹配多个正则表达式来筛选日志文件 | ||
文本模式替换 |
字符串解析
实例名称 | Crates | 类别 |
---|---|---|
收集 Unicode 字符 | ||
自定义结构体 并实现 FromStr trait |
Web 编程
抓取网页
实例名称 | Crates | 类别 |
---|---|---|
从 HTML 网页中提取所有链接 | ||
检查网页死链 | ||
从 MediaWiki 标记页面提取所有唯一性链接 |
URL
实例名称 | Crates | 类别 |
---|---|---|
解析 URL 字符串为 Url 类型 | ||
通过移除路径段创建基本 URL | ||
从基本 URL 创建新 URLs | ||
提取 URL 源(scheme/ host/ port) | ||
从 URL 移除片段标识符和查询对 |
媒体类型(MIME)
实例名称 | Crates | 类别 |
---|---|---|
从字符串获取 MIME 类型 | ||
从文件名获取 MIME 类型 | ||
解析 HTTP 响应的 MIME 类型 |
客户端
请求处理
实例名称 | Crates | 类别 |
---|---|---|
发出 HTTP GET 请求 | ||
为 REST 请求设置自定义消息标头和 URL 参数 |
Web API 调用
实例名称 | Crates | 类别 |
---|---|---|
查询 GitHub API | ||
检查 API 资源是否存在 | ||
使用 GitHub API 创建和删除 Gist | ||
使用 RESTful API 分页 | ||
处理速率受限 API |
下载
实例名称 | Crates | 类别 |
---|---|---|
下载文件到临时目录 | ||
使用 HTTP range 请求头进行部分下载 | ||
POST 文件到 paste-rs |
关于本书
目录
指南目标读者
本指南适用于 Rust 程序员新手,以便于他们可以快速了解 Rust crate 生态系统的功能。同时,本指南也适用于经验丰富的 Rust 程序员,他们可以在本指南中找到如何完成常见任务的简单提示。
如何阅读指南
指南的索引包含完整的实例列表,组织为数个章节:基础、编码、并发等。这些章节基本是按照顺序排列的;后面的章节更深入一些,并且有些实例是在前面章节的概念之上构建。
在索引中,每个章节都包含实例列表。实例名称是要完成任务的简单描述,比如“在一个范围内生成随机数”;每个实例都有标记指示所使用的 crates,比如 ;以及 crate 在 crates.io 上的分类,比如
。
Rust 程序员新手应该按照由第一章节直至最后章节的顺序来阅读,这种方式易于理解书中的实例。同时,这样也可以对 crate 生态系统有一个全面的了解。点击索引中的章节标题,或者在侧边栏中导航到指南的章节页面。
如果你只是在简单地为一个任务的寻找解决方案,那么指南就较难以导航。找到特定实例的最简单的方法是详细查看索引,寻找感兴趣的 crate 及其类别,然后点击实例的名称来阅读它。指南的导航和浏览还在改进,以后或许会有所改善。
如何使用实例
指南的设计是为了让你能够即时访问可工作的代码,以及对其正在做什么有一个完整阐述,并指导你了解如何更进一步的信息。
指南中的所有实例都是完整的、可独立运行的程序,因此你可以直接复制它们到自己的项目中进行试验。为此,请按照以下说明进行操作。
考虑这个实例:“在一个范围内,生成随机数”:
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
println!("Random f64: {}", rng.gen::<f64>());
}
欲在本地使用,我们可以运行以下命令来创建一个新的 cargo 项目,并切换到该目录:
cargo new my-example --bin
cd my-example
然后,我们还需要添加必要的 crate 到 Cargo.toml 中(译者注:了解更多请阅读 Cargo 中文文档,关于 Cargo.toml 详细信息也可参阅 Cargo.toml 与 Cargo.lock 中文文档),如上面实例代码顶部的 crate 标志所示,在本实例中仅使用了 “rand” crate。为了增加 “rand” crate,我们将使用 cargo add
命令,该命令由 cargo-edit
crate 提供,我们需要先安装它:
cargo install cargo-edit
cargo add rand
接下来,可以使用实例代码替换 src/main.rs
文件的全部内容,并通过如下命令运行:
cargo run
亲,成功执行了吧?你已经是一个 Rustacean(Rust 开发者)了!——此处应该有掌声 :-)
上面实例代码顶部的 crate 标志,链接到了 crate 在站点 docs.rs 上的完整文档,通常你在决定使用那个 crate 组件后,应该阅读一下它的文档。
关于错误处理
正确处理时,Rust 中的错误处理是健壮的,但在现今的 Rust 中,它需要大量的模板文件。正因为如此,你会发现 Rust 实例中经常充满了 unwrap
调用,而不是正确的错误处理。
由于本指南旨在提供原样重用的实例,并鼓励最佳实践。因此当涉及到 Result
类型时,它们可以正确地设置错误处理。
我们使用的基本模式是有一个 fn main() -> Result
函数签名。
错误处理的结构,通常如下:
use error_chain::error_chain;
use std::net::IpAddr;
use std::str;
error_chain! {
foreign_links {
Utf8(std::str::Utf8Error);
AddrParse(std::net::AddrParseError);
}
}
fn main() -> Result<()> {
let bytes = b"2001:db8::1";
// Bytes 格式化为 string
let s = str::from_utf8(bytes)?;
// String 解析为 IP address
let addr: IpAddr = s.parse()?;
println!("{:?}", addr);
Ok(())
}
使用 error_chain!
宏自定义 Error
和 Result
类型,以及来自两种标准库的错误类型的自动转换。自动转换使得 ?
操作符正常运作。
在默认情况下——基于可读性目的——错误处理模板是隐藏的,如下代码所示。要阅读完整代码,请单击位于代码段右上角的“展开”()按钮。
要了解更多关于 Rust 错误处理的背景知识,请阅读 Rust 程序设计语言(2018)中的错误处理章节,以及这篇博客文章。
关于 crate 的说明
本指南的最终目的是对 Rust crate 生态系统的广泛覆盖,但目前还处于引导和演示过程中,因此覆盖范围有限。我们希望从小范围开始,然后慢慢拓展,这将有助于指南更快地成为高质量的资源,并使其在增长过程中保持一致的质量水平。
目前,本指南聚焦在标准库、以及“核心”或“基础”方面的 crate ——这些 crate 构成了最常见的编程任务,而生态系统的其它部分则是以此为基础构建的。
本指南与 Rust Libz Blitz 密切相关,后者是一个用于识别并提高 crate 质量的项目,因此它在很大程度上推迟了 crate 的选择。任何已经走完评估过程的 crate,都在本指南的撰写范围内,而且等待评估的 crate 也是如此。
算法
生成随机值
实例名称 | Crates | 类别 |
---|---|---|
生成随机数 | ||
生成范围内随机数 | ||
生成给定分布随机数 | ||
生成自定义类型随机值 | ||
从一组字母数字字符创建随机密码 | ||
从一组用户定义字符创建随机密码 |
Vector 排序
实例名称 | Crates | 类别 |
---|---|---|
整数 Vector 排序 | ||
浮点数 Vector 排序 | ||
结构体 Vector 排序 |
生成随机值
生成随机数
在随机数生成器 rand::Rng
的帮助下,通过 rand::thread_rng
生成随机数。可以开启多个线程,每个线程都有一个初始化的生成器。整数在其类型范围内均匀分布,浮点数是从 0 均匀分布到 1,但不包括 1。
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
let n1: u8 = rng.gen();
let n2: u16 = rng.gen();
println!("Random u8: {}", n1);
println!("Random u16: {}", n2);
println!("Random u32: {}", rng.gen::<u32>());
println!("Random i32: {}", rng.gen::<i32>());
println!("Random float: {}", rng.gen::<f64>());
}
生成范围内随机数
使用 Rng::gen_range
,在半开放的 [0, 10)
范围内(不包括 10
)生成一个随机值。
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
println!("Integer: {}", rng.gen_range(0..10));
println!("Float: {}", rng.gen_range(0.0..10.0));
}
使用 Uniform
模块可以得到均匀分布的值。下述代码和上述代码具有相同的效果,但在相同范围内重复生成数字时,下述代码性能可能会更好。
use rand::distributions::{Distribution, Uniform};
fn main() {
let mut rng = rand::thread_rng();
let die = Uniform::from(1..7);
loop {
let throw = die.sample(&mut rng);
println!("Roll the die: {}", throw);
if throw == 6 {
break;
}
}
}
生成给定分布随机数
默认情况下,随机数在 rand
crate 中是均匀分布。rand_distr
crate 提供其它的分布类型。如要使用,首先实例化一个分布,然后在随机数生成器 rand::Rng
的帮助下,使用 Distribution::sample
从该分布中进行采样。
关于更多信息,阅读可用分布文档。如下是一个使用正态(Normal)
分布的实例。
use rand_distr::{Distribution, Normal, NormalError};
use rand::thread_rng;
fn main() -> Result<(), NormalError> {
let mut rng = thread_rng();
let normal = Normal::new(2.0, 3.0)?;
let v = normal.sample(&mut rng);
println!("{} is from a N(2, 9) distribution", v);
Ok(())
}
生成自定义类型随机值
随机生成一个元组 (i32, bool, f64)
和用户定义类型为 Point
的变量。为 Standard
实现 Distribution
trait,以允许随机生成。
use rand::Rng;
use rand::distributions::{Distribution, Standard};
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Distribution<Point> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Point {
let (rand_x, rand_y) = rng.gen();
Point {
x: rand_x,
y: rand_y,
}
}
}
fn main() {
let mut rng = rand::thread_rng();
let rand_tuple = rng.gen::<(i32, bool, f64)>();
let rand_point: Point = rng.gen();
println!("Random tuple: {:?}", rand_tuple);
println!("Random Point: {:?}", rand_point);
}
从一组字母数字字符创建随机密码
随机生成一个给定长度的 ASCII 字符串,范围为 A-Z,a-z,0-9
,使用字母数字样本。
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
fn main() {
let rand_string: String = thread_rng()
.sample_iter(&Alphanumeric)
.take(30)
.map(char::from)
.collect();
println!("{}", rand_string);
}
从一组用户定义字符创建随机密码
使用用户自定义的字节字符串,使用 gen_range
函数,随机生成一个给定长度的 ASCII 字符串。
fn main() {
use rand::Rng;
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789)(*&^%$#@!~";
const PASSWORD_LEN: usize = 30;
let mut rng = rand::thread_rng();
let password: String = (0..PASSWORD_LEN)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect();
println!("{:?}", password);
}
Vector 排序
整数 Vector 排序
这个实例通过 vec::sort
对一个整数 Vector 进行排序。另一种方法是使用 vec::sort_unstable
,后者运行速度更快一些,但不保持相等元素的顺序。
fn main() {
let mut vec = vec![1, 5, 10, 2, 15];
vec.sort();
assert_eq!(vec, vec![1, 2, 5, 10, 15]);
}
浮点数 Vector 排序
f32 或 f64 的 vector,可以使用 vec::sort_by
和 PartialOrd::partial_cmp
对其进行排序。
fn main() {
let mut vec = vec![1.1, 1.15, 5.5, 1.123, 2.0];
vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert_eq!(vec, vec![1.1, 1.123, 1.15, 2.0, 5.5]);
}
结构体 Vector 排序
依据自然顺序(按名称和年龄),对具有 name
和 age
属性的 Person 结构体 Vector 排序。为了使 Person 可排序,你需要四个 traits:Eq
、PartialEq
、Ord
,以及 PartialOrd
。这些 traits 可以被简单地派生。你也可以使用 vec:sort_by
方法自定义比较函数,仅按照年龄排序。
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
struct Person {
name: String,
age: u32
}
impl Person {
pub fn new(name: String, age: u32) -> Self {
Person {
name,
age
}
}
}
fn main() {
let mut people = vec![
Person::new("Zoe".to_string(), 25),
Person::new("Al".to_string(), 60),
Person::new("John".to_string(), 1),
];
// 根据获得的自然顺序(name 和 age)对 people 进行排序
people.sort();
assert_eq!(
people,
vec![
Person::new("Al".to_string(), 60),
Person::new("John".to_string(), 1),
Person::new("Zoe".to_string(), 25),
]);
// 根据 age 值对 people 进行排序
people.sort_by(|a, b| b.age.cmp(&a.age));
assert_eq!(
people,
vec![
Person::new("Al".to_string(), 60),
Person::new("Zoe".to_string(), 25),
Person::new("John".to_string(), 1),
]);
}
命令行
参数解析
实例名称 | Crates | 类别 |
---|---|---|
解析命令行参数 |
ANSI 终端
实例名称 | Crates | 类别 |
---|---|---|
ANSI 终端 |
参数解析
解析命令行参数
此应用程序使用 clap
构建器样式描述其命令行界面的结构。文档还提供了另外两种可用的方法去实例化应用程序。
在构建器样式中,with_name
函数是 value_of
方法将用于检索传递值的唯一标识符。short
和 long
选项控制用户将要键入的标志;short 标志看起来像 -f
,long 标志看起来像 --file
。
use clap::{Arg, App};
fn main() {
let matches = App::new("My Test Program")
.version("0.1.0")
.author("Hackerman Jones <hckrmnjones@hack.gov>")
.about("Teaches argument parsing")
.arg(Arg::with_name("file")
.short("f")
.long("file")
.takes_value(true)
.help("A cool file"))
.arg(Arg::with_name("num")
.short("n")
.long("number")
.takes_value(true)
.help("Five less than your favorite number"))
.get_matches();
let myfile = matches.value_of("file").unwrap_or("input.txt");
println!("The file passed is: {}", myfile);
let num_str = matches.value_of("num");
match num_str {
None => println!("No idea what your favorite number is."),
Some(s) => {
match s.parse::<i32>() {
Ok(n) => println!("Your favorite number must be {}.", n + 5),
Err(_) => println!("That's not a number! {}", s),
}
}
}
}
使用信息由 clap
生成。实例应用程序的用法如下所示。
My Test Program 0.1.0
Hackerman Jones <hckrmnjones@hack.gov>
Teaches argument parsing
USAGE:
testing [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-f, --file <file> A cool file
-n, --number <num> Five less than your favorite number
我们可以通过运行如下命令来测试应用程序。
$ cargo run -- -f myfile.txt -n 251
输出为:
The file passed is: myfile.txt
Your favorite number must be 256.
ANSI 终端
ANSI 终端
此程序描述了 ansi_term
crate 的使用方法,以及它如何用于控制 ANSI 终端上的颜色和格式,如蓝色粗体文本或黄色下划线文本。
ansi_term
中有两种主要的数据结构:ANSIString
和 Style
。Style
包含样式信息:颜色,是否粗体文本,或者是否闪烁,或者其它样式。还有 Colour 变量,代表简单的前景色样式。ANSIString
是与 Style
配对的字符串。
注意:英式英语中使用 Colour 而不是 Color,不要混淆。
打印彩色文本到终端
use ansi_term::Colour;
fn main() {
println!("This is {} in color, {} in color and {} in color",
Colour::Red.paint("red"),
Colour::Blue.paint("blue"),
Colour::Green.paint("green"));
}
终端中的粗体文本
对于比简单的前景色变化更复杂的事情,代码需要构造 Style
结构体。Style::new()
创建结构体,并链接属性。
use ansi_term::Style;
fn main() {
println!("{} and this is not",
Style::new().bold().paint("This is Bold"));
}
终端中的粗体和彩色文本
Colour
模块实现了许多类似 Style
的函数,并且可以链接方法。
use ansi_term::Colour;
use ansi_term::Style;
fn main(){
println!("{}, {} and {}",
Colour::Yellow.paint("This is colored"),
Style::new().bold().paint("this is bold"),
Colour::Yellow.bold().paint("this is bold and colored"));
}
压缩
使用 tar 包
实例名称 | Crates | 类别 |
---|---|---|
解压 tar 包 | ||
压缩目录为 tar 包 | ||
从路径移除前缀时,解压 tar 包 |
使用 tar 包
解压 tar 包
从当前工作目录中的压缩包 archive.tar.gz
,解压(GzDecoder
)和提取(Archive::unpack
)所有文件,并放在同一位置。
use std::fs::File;
use flate2::read::GzDecoder;
use tar::Archive;
fn main() -> Result<(), std::io::Error> {
let path = "archive.tar.gz";
let tar_gz = File::open(path)?;
let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar);
archive.unpack(".")?;
Ok(())
}
压缩目录为 tar 包
压缩 /var/log
目录内的内容到 archive.tar.gz
压缩包中。
创建一个用 GzEncoder
和 tar::Builder
包裹的 File
。
使用 Builder::append_dir_all
,将 /var/log
目录内的内容递归添加到 backup/logs
路径下的归档文件中。在将数据写入压缩包 archive.tar.gz
之前,GzEncoder
负责清晰地将数据压缩。
use std::fs::File;
use flate2::Compression;
use flate2::write::GzEncoder;
fn main() -> Result<(), std::io::Error> {
let tar_gz = File::create("archive.tar.gz")?;
let enc = GzEncoder::new(tar_gz, Compression::default());
let mut tar = tar::Builder::new(enc);
tar.append_dir_all("backup/logs", "/var/log")?;
Ok(())
}
从路径移除前缀时,解压 tar 包
循环遍历 Archive::entries
。使用 Path::strip_prefix
移除指定的路径前缀(bundle/logs
)。最终,通过 Entry::unpack
提取 tar::Entry
(tar 包中的内容)。
并发/并行
显式线程
实例名称 | Crates | 类别 |
---|---|---|
生成短期线程 | ||
创建并发的数据管道 | ||
在两个线程间传递数据 | ||
保持全局可变状态 | ||
对所有 iso 文件的 SHA256 值并发求和 | ||
将绘制分形的线程分派到线程池 |
数据并行
实例名称 | Crates | 类别 |
---|---|---|
并行改变数组中元素 | ||
并行测试集合中任意或所有的元素是否匹配给定断言 | ||
使用给定断言并行搜索项 | ||
对 vector 并行排序 | ||
Map-reduce 并行计算 | ||
并行生成 jpg 缩略图 |
显式线程
生成短期线程
本实例使用 crossbeam crate 为并发和并行编程提供了数据结构和函数。Scope::spawn
生成一个新的作用域线程,该线程确保传入 crossbeam::scope
函数的闭包在返回之前终止,这意味着您可以从调用的函数中引用数据。
本实例将数组一分为二,并在不同的线程中并行计算。
fn main() {
let arr = &[1, 25, -4, 10];
let max = find_max(arr);
assert_eq!(max, Some(25));
}
fn find_max(arr: &[i32]) -> Option<i32> {
const THRESHOLD: usize = 2;
if arr.len() <= THRESHOLD {
return arr.iter().cloned().max();
}
let mid = arr.len() / 2;
let (left, right) = arr.split_at(mid);
crossbeam::scope(|s| {
let thread_l = s.spawn(|_| find_max(left));
let thread_r = s.spawn(|_| find_max(right));
let max_l = thread_l.join().unwrap()?;
let max_r = thread_r.join().unwrap()?;
Some(max_l.max(max_r))
}).unwrap()
}
创建并发的数据管道
下面的实例使用 crossbeam 和 crossbeam-channel 两个 crate 创建了一个并行的管道,与 ZeroMQ 指南 中所描述的类似:管道有一个数据源和一个数据接收器,数据在从源到接收器的过程中由两个工作线程并行处理。
我们使用容量由 crossbeam_channel::bounded
分配的有界信道。生产者必须在它自己的线程上,因为它产生的消息比工作线程处理它们的速度快(因为工作线程休眠了半秒)——这意味着生产者将在对 [crossbeam_channel::Sender::send
] 调用时阻塞半秒,直到其中一个工作线程对信道中的数据处理完毕。也请注意,信道中的数据由最先接收它的任何工作线程调用,因此每个消息都传递给单个工作线程,而不是传递给两个工作线程。
通过迭代器 crossbeam_channel::Receiver::iter
方法从信道读取数据,这将会造成阻塞,要么等待新消息,要么直到信道关闭。因为信道是在 crossbeam::scope
范围内创建的,我们必须通过 drop
手动关闭它们,以防止整个程序阻塞工作线程的 for 循环。你可以将对 drop
的调用视作不再发送消息的信号。
extern crate crossbeam;
extern crate crossbeam_channel;
use std::thread;
use std::time::Duration;
use crossbeam_channel::bounded;
fn main() {
let (snd1, rcv1) = bounded(1);
let (snd2, rcv2) = bounded(1);
let n_msgs = 4;
let n_workers = 2;
crossbeam::scope(|s| {
// 生产者线程
s.spawn(|_| {
for i in 0..n_msgs {
snd1.send(i).unwrap();
println!("Source sent {}", i);
}
// 关闭信道 —— 这是退出的必要条件
// for 巡海在工作线程中
drop(snd1);
});
// 由 2 个县城并行处理
for _ in 0..n_workers {
// 从数据源发送数据到接收器,接收器接收数据
let (sendr, recvr) = (snd2.clone(), rcv1.clone());
// 在不同的线程中衍生工人
s.spawn(move |_| {
thread::sleep(Duration::from_millis(500));
// 接收数据,直到信道关闭前
for msg in recvr.iter() {
println!("Worker {:?} received {}.",
thread::current().id(), msg);
sendr.send(msg * 2).unwrap();
}
});
}
// 关闭信道,否则接收器不会关闭
// 退出 for 循坏
drop(snd2);
// 接收器
for msg in rcv2.iter() {
println!("Sink received {}", msg);
}
}).unwrap();
}
在两个线程间传递数据
这个实例示范了在单生产者、单消费者(SPSC)环境中使用 crossbeam-channel。我们构建的生成短期线程实例中,使用 crossbeam::scope
和 Scope::spawn
来管理生产者线程。在两个线程之间,使用 crossbeam_channel::unbounded
信道交换数据,这意味着可存储消息的数量没有限制。生产者线程在消息之间休眠半秒。
use std::{thread, time};
use crossbeam_channel::unbounded;
fn main() {
let (snd, rcv) = unbounded();
let n_msgs = 5;
crossbeam::scope(|s| {
s.spawn(|_| {
for i in 0..n_msgs {
snd.send(i).unwrap();
thread::sleep(time::Duration::from_millis(100));
}
});
}).unwrap();
for _ in 0..n_msgs {
let msg = rcv.recv().unwrap();
println!("Received {}", msg);
}
}
保持全局可变状态
使用 lazy_static 声明全局状态。lazy_static 创建了一个全局可用的 static ref
,它需要 Mutex
来允许变化(请参阅 RwLock
)。在 Mutex
的包裹下,保证了状态不能被多个线程同时访问,从而防止出现争用情况。必须获取 MutexGuard
,方可读取或更改存储在 Mutex
中的值。
对所有 iso 文件的 SHA256 值并发求和
下面的实例计算了当前目录中每个扩展名为 iso 的文件的 SHA256 哈希值。线程池生成的线程数与使用 num_cpus::get
获取的系统内核数相等。Walkdir::new
遍历当前目录,并调用 execute
来执行读取和计算 SHA256 哈希值的操作。
将绘制分形的线程分派到线程池
此实例通过从朱莉娅集绘制分形来生成图像,该集合具有用于分布式计算的线程池。
使用 ImageBuffer::new
为指定宽度和高度的输出图像分配内存,Rgb::from_channels
信道则计算输出图像的 RGB 像素值。使用 ThreadPool
创建线程池,线程池中的线程数量和使用 num_cpus::get
获取的系统内核数相等。ThreadPool::execute
将每个像素作为单独的作业接收。
mpsc::channel
信道接收作业,Receiver::recv
接收器则检索作业。ImageBuffer::put_pixel
处理数据,设置像素颜色。最后,ImageBuffer::save
将图像存储为 output.png
。
数据并行
并行改变数组中元素
下面的实例使用了 rayon
crate,这是一个 Rust 程序设计语言的数据并行库。rayon
为任何并行可迭代的数据类型提供 par_iter_mut
方法。这是一个类迭代器的链,可以对链内的数据并行计算。
use rayon::prelude::*;
fn main() {
let mut arr = [0, 7, 9, 11];
arr.par_iter_mut().for_each(|p| *p -= 1);
println!("{:?}", arr);
}
并行测试集合中任意或所有的元素是否匹配给定断言
这个实例示范如何使用 rayon::any
和 rayon::all
方法,这两个方法是分别与 std::any
和 std::all
相对应的并行方法。rayon::any
并行检查迭代器的任意元素是否与断言匹配,并在找到一个匹配的元素时就返回。rayon::all
并行检查迭代器的所有元素是否与断言匹配,并在找到不匹配的元素时立即返回。
use rayon::prelude::*;
fn main() {
let mut vec = vec![2, 4, 6, 8];
assert!(!vec.par_iter().any(|n| (*n % 2) != 0));
assert!(vec.par_iter().all(|n| (*n % 2) == 0));
assert!(!vec.par_iter().any(|n| *n > 8 ));
assert!(vec.par_iter().all(|n| *n <= 8 ));
vec.push(9);
assert!(vec.par_iter().any(|n| (*n % 2) != 0));
assert!(!vec.par_iter().all(|n| (*n % 2) == 0));
assert!(vec.par_iter().any(|n| *n > 8 ));
assert!(!vec.par_iter().all(|n| *n <= 8 ));
}
使用给定断言并行搜索项
下面的实例使用 rayon::find_any
和 par_iter
并行搜索 vector 集合,以查找满足指定闭包中的断言的元素。
如果有多个元素满足 rayon::find_any
闭包参数中定义的断言,rayon
将返回搜索发现的第一个元素,但不一定是 vector 集合的第一个元素。
请注意,实例中闭包的参数是对引用的引用(&&x
)。有关更多详细信息,请参阅关于 std::find
的讨论。
use rayon::prelude::*;
fn main() {
let v = vec![6, 2, 1, 9, 3, 8, 11];
let f1 = v.par_iter().find_any(|&&x| x == 9);
let f2 = v.par_iter().find_any(|&&x| x % 2 == 0 && x > 6);
let f3 = v.par_iter().find_any(|&&x| x > 8);
assert_eq!(f1, Some(&9));
assert_eq!(f2, Some(&8));
assert!(f3 > Some(&8));
}
对 vector 并行排序
本实例对字符串 vector 并行排序。
首先,分配空字符串 vector;然后,通过 par_iter_mut().for_each
并行对 vector 填充随机值。尽管存在多种选择,可以对可枚举数据类型进行排序,但 par_sort_unstable
通常比稳定排序(相同的值排序后相对顺序不变)算法快。
use rand::{Rng, thread_rng};
use rand::distributions::Alphanumeric;
use rayon::prelude::*;
fn main() {
let mut vec = vec![String::new(); 100_000];
vec.par_iter_mut().for_each(|p| {
let mut rng = thread_rng();
*p = (0..5).map(|_| rng.sample(&Alphanumeric)).collect()
});
vec.par_sort_unstable();
}
Map-reduce 并行计算
此实例使用 rayon::filter
、rayon::map
,以及 rayon::reduce
计算 Person
对象中年龄超过 30 岁的那些人的平均年龄。
rayon::filter
过滤集合中满足给定断言的元素。rayon::map
对每个元素执行一次计算,创建一个新的迭代;然后,基于前一次的 reduce 计算结果和当前元素一起,rayon::reduce
执行新的计算。也可以查看 rayon::sum
,它与本实例中的 reduce 计算具有相同的结果。
use rayon::prelude::*;
struct Person {
age: u32,
}
fn main() {
let v: Vec<Person> = vec![
Person { age: 23 },
Person { age: 19 },
Person { age: 42 },
Person { age: 17 },
Person { age: 17 },
Person { age: 31 },
Person { age: 30 },
];
let num_over_30 = v.par_iter().filter(|&x| x.age > 30).count() as f32;
let sum_over_30 = v.par_iter()
.map(|x| x.age)
.filter(|&x| x > 30)
.reduce(|| 0, |x, y| x + y);
let alt_sum_30: u32 = v.par_iter()
.map(|x| x.age)
.filter(|&x| x > 30)
.sum();
let avg_over_30 = sum_over_30 as f32 / num_over_30;
let alt_avg_over_30 = alt_sum_30 as f32/ num_over_30;
assert!((avg_over_30 - alt_avg_over_30).abs() < std::f32::EPSILON);
println!("The average age of people older than 30 is {}", avg_over_30);
}
并行生成 jpg 缩略图
本实例为当前目录中的所有 .jpg 图像文件生成缩略图,然后将生成的缩略图保存在一个名为 thumbnails
的新文件夹中。
glob::glob_with
在当前目录中查找 jpeg 图像文件,rayon
通过 par_iter
方法调用 DynamicImage::resize
,并行地调整图像大小。
密码学
散列(哈希)
实例名称 | Crates | 类别 |
---|---|---|
计算文件的 SHA-256 摘要 | ||
使用 HMAC 摘要对消息进行签名和验证 |
加密
实例名称 | Crates | 类别 |
---|---|---|
使用 PBKDF2 对密码进行加密(salt)和散列(hash)运算 |
散列(哈希)
计算文件的 SHA-256 摘要
如下实例中,先创建文件,写入一些数据。然后使用 digest::Context
计算文件内容的 SHA-256 摘要 digest::Digest
。
使用 HMAC 摘要对消息进行签名和验证
使用 ring::hmac
创建字符串的签名 hmac::Signature
,然后验证签名是否正确。
use ring::{hmac, rand};
use ring::rand::SecureRandom;
use ring::error::Unspecified;
fn main() -> Result<(), Unspecified> {
let mut key_value = [0u8; 48];
let rng = rand::SystemRandom::new();
rng.fill(&mut key_value)?;
let key = hmac::Key::new(hmac::HMAC_SHA256, &key_value);
let message = "Legitimate and important message.";
let signature = hmac::sign(&key, message.as_bytes());
hmac::verify(&key, message.as_bytes(), signature.as_ref())?;
Ok(())
}
加密
使用 PBKDF2 对密码进行加密(salt)和散列(hash)运算
对于通过 PBKDF2 密钥派生函数 pbkdf2::derive
生成的加密(加盐算法)密码,使用 ring::pbkdf2
进行散列(哈希)运算,使用 pbkdf2::verify
验证散列(哈希)运算是否正确。salt 值是使用 SecureRandom::fill
生成的,salt 字节数组被其安全生成的随机数填充。
use data_encoding::HEXUPPER;
use ring::error::Unspecified;
use ring::rand::SecureRandom;
use ring::{digest, pbkdf2, rand};
use std::num::NonZeroU32;
fn main() -> Result<(), Unspecified> {
const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN;
let n_iter = NonZeroU32::new(100_000).unwrap();
let rng = rand::SystemRandom::new();
let mut salt = [0u8; CREDENTIAL_LEN];
rng.fill(&mut salt)?;
let password = "Guess Me If You Can!";
let mut pbkdf2_hash = [0u8; CREDENTIAL_LEN];
pbkdf2::derive(
pbkdf2::PBKDF2_HMAC_SHA512,
n_iter,
&salt,
password.as_bytes(),
&mut pbkdf2_hash,
);
println!("Salt: {}", HEXUPPER.encode(&salt));
println!("PBKDF2 hash: {}", HEXUPPER.encode(&pbkdf2_hash));
let should_succeed = pbkdf2::verify(
pbkdf2::PBKDF2_HMAC_SHA512,
n_iter,
&salt,
password.as_bytes(),
&pbkdf2_hash,
);
let wrong_password = "Definitely not the correct password";
let should_fail = pbkdf2::verify(
pbkdf2::PBKDF2_HMAC_SHA512,
n_iter,
&salt,
wrong_password.as_bytes(),
&pbkdf2_hash,
);
assert!(should_succeed.is_ok());
assert!(!should_fail.is_ok());
Ok(())
}
数据结构
位域
实例名称 | Crates | 类别 |
---|---|---|
定义并操作位域风格的类型 |
位域
定义并操作位域风格的类型
如下实例在 bitflags!
宏的帮助下创建类型安全的位域类型 MyFlags
,并为其实现基本的清理
操作(clear
方法)以及 Display
trait。随后,展示了基本的按位操作和格式化。
use bitflags::bitflags;
use std::fmt;
bitflags! {
struct MyFlags: u32 {
const FLAG_A = 0b00000001;
const FLAG_B = 0b00000010;
const FLAG_C = 0b00000100;
const FLAG_ABC = Self::FLAG_A.bits
| Self::FLAG_B.bits
| Self::FLAG_C.bits;
}
}
impl MyFlags {
pub fn clear(&mut self) -> &mut MyFlags {
self.bits = 0;
self
}
}
impl fmt::Display for MyFlags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:032b}", self.bits)
}
}
fn main() {
let e1 = MyFlags::FLAG_A | MyFlags::FLAG_C;
let e2 = MyFlags::FLAG_B | MyFlags::FLAG_C;
assert_eq!((e1 | e2), MyFlags::FLAG_ABC);
assert_eq!((e1 & e2), MyFlags::FLAG_C);
assert_eq!((e1 - e2), MyFlags::FLAG_A);
assert_eq!(!e2, MyFlags::FLAG_A);
let mut flags = MyFlags::FLAG_ABC;
assert_eq!(format!("{}", flags), "00000000000000000000000000000111");
assert_eq!(format!("{}", flags.clear()), "00000000000000000000000000000000");
assert_eq!(format!("{:?}", MyFlags::FLAG_B), "FLAG_B");
assert_eq!(format!("{:?}", MyFlags::FLAG_A | MyFlags::FLAG_B), "FLAG_A | FLAG_B");
}
数据库
SQLite
实例名称 | Crates | 类别 |
---|---|---|
创建 SQLite 数据库 | ||
数据插入和查询 | ||
事务处理 |
使用 Postgres
实例名称 | Crates | 类别 |
---|---|---|
Postgres 数据库中创建表 | ||
数据插入和查询 | ||
数据聚合 |
SQLite
创建 SQLite 数据库
使用 rusqlite
crate 打开 SQLite 数据库连接。Windows 上编译 rusqlite
crate 请参考文档。
如果数据库不存在,Connection::open
方法将创建它。
use rusqlite::{Connection, Result};
use rusqlite::NO_PARAMS;
fn main() -> Result<()> {
let conn = Connection::open("cats.db")?;
conn.execute(
"create table if not exists cat_colors (
id integer primary key,
name text not null unique
)",
NO_PARAMS,
)?;
conn.execute(
"create table if not exists cats (
id integer primary key,
name text not null,
color_id integer not null references cat_colors(id)
)",
NO_PARAMS,
)?;
Ok(())
}
数据插入和查询
Connection::open
将打开在前一章节实例中创建的数据库 cats
的连接。下面的实例使用 Connection
的 execute
方法将数据插入 cat_colors
和 cats
两张表中。首先,将数据插入到 cat_colors
表中。随后,使用 Connection
的 last_insert_rowid
方法来获取 cat_colors
表最后插入记录的 id
。当向 cats
表中插入数据时,使用此 id
。然后,使用 prepare
方法准备执行 select 查询操作,该方法提供 statement
结构体。最后,使用 statement
的 query_map
方法执行查询。
use rusqlite::NO_PARAMS;
use rusqlite::{Connection, Result};
use std::collections::HashMap;
#[derive(Debug)]
struct Cat {
name: String,
color: String,
}
fn main() -> Result<()> {
let conn = Connection::open("cats.db")?;
let mut cat_colors = HashMap::new();
cat_colors.insert(String::from("Blue"), vec!["Tigger", "Sammy"]);
cat_colors.insert(String::from("Black"), vec!["Oreo", "Biscuit"]);
for (color, catnames) in &cat_colors {
conn.execute(
"INSERT INTO cat_colors (name) values (?1)",
&[&color.to_string()],
)?;
let last_id: String = conn.last_insert_rowid().to_string();
for cat in catnames {
conn.execute(
"INSERT INTO cats (name, color_id) values (?1, ?2)",
&[&cat.to_string(), &last_id],
)?;
}
}
let mut stmt = conn.prepare(
"SELECT c.name, cc.name from cats c
INNER JOIN cat_colors cc
ON cc.id = c.color_id;",
)?;
let cats = stmt.query_map(NO_PARAMS, |row| {
Ok(Cat {
name: row.get(0)?,
color: row.get(1)?,
})
})?;
for cat in cats {
println!("Found cat {:?}", cat);
}
Ok(())
}
事务处理
Connection::open
将打开来自前述实例的数据库 cats.db
。
使用 Connection::transaction
开始事务,除非使用 Transaction::commit
显式提交,否则事务将回滚。
在下面的实例中,颜色表对颜色名称具有唯一性约束。当尝试插入重复的颜色时,事务会回滚。
use rusqlite::{Connection, Result, NO_PARAMS};
fn main() -> Result<()> {
let mut conn = Connection::open("cats.db")?;
successful_tx(&mut conn)?;
let res = rolled_back_tx(&mut conn);
assert!(res.is_err());
Ok(())
}
fn successful_tx(conn: &mut Connection) -> Result<()> {
let tx = conn.transaction()?;
tx.execute("delete from cat_colors", NO_PARAMS)?;
tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?;
tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?;
tx.commit()
}
fn rolled_back_tx(conn: &mut Connection) -> Result<()> {
let tx = conn.transaction()?;
tx.execute("delete from cat_colors", NO_PARAMS)?;
tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?;
tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?;
tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?;
tx.commit()
}
使用 Postgres
Postgres 数据库中创建表
Postgres 数据库中,使用 postgres
crate 创建表。
Client::connect
用于连接到现有数据库。本实例中使用 Client::connect
格式化连接数据库的 URL 字符串。假设存在一个数据库:名为 library
,用户名为 postgres
,密码为 postgres
。
use postgres::{Client, NoTls, Error};
fn main() -> Result<(), Error> {
let mut client = Client::connect("postgresql://postgres:postgres@localhost/library", NoTls)?;
client.batch_execute("
CREATE TABLE IF NOT EXISTS author (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
country VARCHAR NOT NULL
)
")?;
client.batch_execute("
CREATE TABLE IF NOT EXISTS book (
id SERIAL PRIMARY KEY,
title VARCHAR NOT NULL,
author_id INTEGER NOT NULL REFERENCES author
)
")?;
Ok(())
}
数据插入和查询
下述实例中使用 Client
的 execute
方法将数据插入到 author
表中。然后,使用 Client
的 query
方法查询 author
表中的数据。
use postgres::{Client, NoTls, Error};
use std::collections::HashMap;
struct Author {
_id: i32,
name: String,
country: String
}
fn main() -> Result<(), Error> {
let mut client = Client::connect("postgresql://postgres:postgres@localhost/library",
NoTls)?;
let mut authors = HashMap::new();
authors.insert(String::from("Chinua Achebe"), "Nigeria");
authors.insert(String::from("Rabindranath Tagore"), "India");
authors.insert(String::from("Anita Nair"), "India");
for (key, value) in &authors {
let author = Author {
_id: 0,
name: key.to_string(),
country: value.to_string()
};
client.execute(
"INSERT INTO author (name, country) VALUES ($1, $2)",
&[&author.name, &author.country],
)?;
}
for row in client.query("SELECT id, name, country FROM author", &[])? {
let author = Author {
_id: row.get(0),
name: row.get(1),
country: row.get(2),
};
println!("Author {} is from {}", author.name, author.country);
}
Ok(())
}
数据聚合
下述实例按照降序列出了美国纽约州现代艺术博物馆
数据库中首批 7999 位艺术家的国籍。
use postgres::{Client, Error, NoTls};
struct Nation {
nationality: String,
count: i64,
}
fn main() -> Result<(), Error> {
let mut client = Client::connect(
"postgresql://postgres:postgres@127.0.0.1/moma",
NoTls,
)?;
for row in client.query
("SELECT nationality, COUNT(nationality) AS count
FROM artists GROUP BY nationality ORDER BY count DESC", &[])? {
let (nationality, count) : (Option<String>, Option<i64>)
= (row.get (0), row.get (1));
if nationality.is_some () && count.is_some () {
let nation = Nation{
nationality: nationality.unwrap(),
count: count.unwrap(),
};
println!("{} {}", nation.nationality, nation.count);
}
}
Ok(())
}
日期及时间
期间和计算
实例名称 | Crates | 类别 |
---|---|---|
测量运行时间 | ||
执行日期检查和时间计算 | ||
时间的时区转换 |
解析与显示
实例名称 | Crates | 类别 |
---|---|---|
检查日期和时间 | ||
日期和 UNIX 时间戳的互相转换 | ||
日期和时间的格式化显示 | ||
将字符串解析为 DateTime 结构体 |
期间和计算
测量运行时间
测量从 time::Instant::now
开始运行的时间 time::Instant::elapsed
。
调用 time::Instant::elapsed
将返回 time::Duration
,我们将在实例末尾打印该时间。此方法不会更改或者重置 time::Instant
对象。
执行日期检查和时间计算
使用 DateTime::checked_add_signed
计算并显示两周之后的日期和时间,使用 DateTime::checked_sub_signed
计算并显示前一天的日期。如果无法计算出日期和时间,这些方法将返回 None。
可以在 chrono::format::strftime
中找到适用于 DateTime::format
的转义序列。
use chrono::{DateTime, Duration, Utc};
fn day_earlier(date_time: DateTime<Utc>) -> Option<DateTime<Utc>> {
date_time.checked_sub_signed(Duration::days(1))
}
fn main() {
let now = Utc::now();
println!("{}", now);
let almost_three_weeks_from_now = now.checked_add_signed(Duration::weeks(2))
.and_then(|in_2weeks| in_2weeks.checked_add_signed(Duration::weeks(1)))
.and_then(day_earlier);
match almost_three_weeks_from_now {
Some(x) => println!("{}", x),
None => eprintln!("Almost three weeks from now overflows!"),
}
match now.checked_add_signed(Duration::max_value()) {
Some(x) => println!("{}", x),
None => eprintln!("We can't use chrono to tell the time for the Solar System to complete more than one full orbit around the galactic center."),
}
}
时间的时区转换
使用 offset::Local::now
获取本地时间并显示,然后使用 DateTime::from_utc
结构体方法将其转换为 UTC 标准格式。最后,使用 offset::FixedOffset
结构体,可以将 UTC 时间转换为 UTC+8 和 UTC-2。
use chrono::{DateTime, FixedOffset, Local, Utc};
fn main() {
let local_time = Local::now();
let utc_time = DateTime::<Utc>::from_utc(local_time.naive_utc(), Utc);
let china_timezone = FixedOffset::east(8 * 3600);
let rio_timezone = FixedOffset::west(2 * 3600);
println!("Local time now is {}", local_time);
println!("UTC time now is {}", utc_time);
println!(
"Time in Hong Kong now is {}",
utc_time.with_timezone(&china_timezone)
);
println!("Time in Rio de Janeiro now is {}", utc_time.with_timezone(&rio_timezone));
}
解析与显示
检查日期和时间
通过 Timelike
获取当前 UTC DateTime
及其时/分/秒,通过 Datelike
获取其年/月/日/工作日。
use chrono::{Datelike, Timelike, Utc};
fn main() {
let now = Utc::now();
let (is_pm, hour) = now.hour12();
println!(
"The current UTC time is {:02}:{:02}:{:02} {}",
hour,
now.minute(),
now.second(),
if is_pm { "PM" } else { "AM" }
);
println!(
"And there have been {} seconds since midnight",
now.num_seconds_from_midnight()
);
let (is_common_era, year) = now.year_ce();
println!(
"The current UTC date is {}-{:02}-{:02} {:?} ({})",
year,
now.month(),
now.day(),
now.weekday(),
if is_common_era { "CE" } else { "BCE" }
);
println!(
"And the Common Era began {} days ago",
now.num_days_from_ce()
);
}
日期和 UNIX 时间戳的互相转换
使用 NaiveDateTime::timestamp
将由 NaiveDate::from_ymd
生成的日期和由 NaiveTime::from_hms
生成的时间转换为 UNIX 时间戳。然后,它使用 NaiveDateTime::from_timestamp
计算自 UTC 时间 1970 年 01 月 01 日 00:00:00 开始的 10 亿秒后的日期。
use chrono::{NaiveDate, NaiveDateTime};
fn main() {
let date_time: NaiveDateTime = NaiveDate::from_ymd(2017, 11, 12).and_hms(17, 33, 44);
println!(
"Number of seconds between 1970-01-01 00:00:00 and {} is {}.",
date_time, date_time.timestamp());
let date_time_after_a_billion_seconds = NaiveDateTime::from_timestamp(1_000_000_000, 0);
println!(
"Date after a billion seconds since 1970-01-01 00:00:00 was {}.",
date_time_after_a_billion_seconds);
}
日期和时间的格式化显示
使用 Utc::now
获取并显示当前 UTC 时间。使用 DateTime::to_rfc2822
将当前时间格式化为熟悉的 RFC 2822 格式,使用 DateTime::to_rfc3339
将当前时间格式化为熟悉的 RFC 3339 格式,也可以使用 DateTime::format
自定义时间格式。
use chrono::{DateTime, Utc};
fn main() {
let now: DateTime<Utc> = Utc::now();
println!("UTC now is: {}", now);
println!("UTC now in RFC 2822 is: {}", now.to_rfc2822());
println!("UTC now in RFC 3339 is: {}", now.to_rfc3339());
println!("UTC now in a custom format is: {}", now.format("%a %b %e %T %Y"));
}
将字符串解析为 DateTime 结构体
熟悉的时间格式 RFC 2822、RFC 3339,以及自定义时间格式,通常用字符串表达。要将这些字符串解析为 DateTime
结构体,可以分别用 DateTime::parse_from_rfc2822
、DateTime::parse_from_rfc3339
,以及 DateTime::parse_from_str
。
可以在 chrono::format::strftime
中找到适用于 DateTime::parse_from_str
的转义序列。注意:DateTime::parse_from_str
要求这些 DateTime 结构体必须是可创建的,以便它唯一地标识日期和时间。要解析不带时区的日期和时间,请使用 NaiveDate
、NaiveTime
,以及 NaiveDateTime
。
use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime};
use chrono::format::ParseError;
fn main() -> Result<(), ParseError> {
let rfc2822 = DateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200")?;
println!("{}", rfc2822);
let rfc3339 = DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00")?;
println!("{}", rfc3339);
let custom = DateTime::parse_from_str("5.8.1994 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")?;
println!("{}", custom);
let time_only = NaiveTime::parse_from_str("23:56:04", "%H:%M:%S")?;
println!("{}", time_only);
let date_only = NaiveDate::parse_from_str("2015-09-05", "%Y-%m-%d")?;
println!("{}", date_only);
let no_timezone = NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")?;
println!("{}", no_timezone);
Ok(())
}
开发工具
调试工具
日志信息
实例名称 | Crates | 类别 |
---|---|---|
记录调试信息到控制台 | ||
记录错误信息到控制台 | ||
记录信息时,用标准输出 stdout 替换标准错误 stderr | ||
使用自定义日志记录器记录信息 | ||
记录到 Unix 系统日志 |
日志配置
实例名称 | Crates | 类别 |
---|---|---|
启用每个模块的日志级别 | ||
用自定义环境变量设置日志记录 | ||
在日志信息中包含时间戳 | ||
将信息记录到自定义位置 |
版本控制
实例名称 | Crates | 类别 |
---|---|---|
解析并递增版本字符串 | ||
解析复杂的版本字符串 | ||
检查给定版本是否为预发布版本 | ||
查询适配给定范围的最新版本 | ||
检查外部命令的版本兼容性 |
构建时
实例名称 | Crates | 类别 |
---|---|---|
编译并静态链接到绑定的 C 语言库 | ||
编译并静态链接到绑定的 C++ 语言库 | ||
编译 C 语言库时自定义设置 |
调试工具
日志信息
实例名称 | Crates | 类别 |
---|---|---|
记录调试信息到控制台 | ||
记录错误信息到控制台 | ||
记录信息时,用标准输出 stdout 替换标准错误 stderr | ||
使用自定义日志记录器记录信息 | ||
记录到 Unix 系统日志 |
日志配置
实例名称 | Crates | 类别 |
---|---|---|
启用每个模块的日志级别 | ||
用自定义环境变量设置日志记录 | ||
在日志信息中包含时间戳 | ||
将信息记录到自定义位置 |
日志信息
记录调试信息到控制台
log
crate 提供了日志工具,env_logger
crate 通过环境变量配置日志记录。log::debug!
宏的工作方式类似于其它 std::fmt
格式化的字符串。
fn execute_query(query: &str) {
log::debug!("Executing query: {}", query);
}
fn main() {
env_logger::init();
execute_query("DROP TABLE students");
}
运行上述代码时,并没有输出信息被打印。因为默认情况下,日志级别为 error
,任何较低级别的日志信息都将被忽略。
设置 RUST_LOG
环境变量以打印消息:
$ RUST_LOG=debug cargo run
Cargo 运行后,会在输出的最后一行打印出调试信息:
DEBUG:main: Executing query: DROP TABLE students
记录错误信息到控制台
正确的错误处理会将异常视为错误。下述实例中,通过 log
便捷宏 log::error!
,将错误记录到 stderr。
fn execute_query(_query: &str) -> Result<(), &'static str> {
Err("I'm afraid I can't do that")
}
fn main() {
env_logger::init();
let response = execute_query("DROP TABLE students");
if let Err(err) = response {
log::error!("Failed to execute query: {}", err);
}
}
记录信息时,用标准输出 stdout 替换标准错误 stderr
使用 Builder::target
创建自定义的日志记录器配置,将日志输出的目标设置为 Target::Stdout
。
use env_logger::{Builder, Target};
fn main() {
Builder::new()
.target(Target::Stdout)
.init();
log::error!("This error has been printed to Stdout");
}
使用自定义日志记录器记录信息
本实例实现一个打印到 stdout 的自定义记录器 ConsoleLogger
。为了使用日志宏,ConsoleLogger
实现了 log::Log
trait,通过 log::set_logger
安置。
use log::{Record, Level, Metadata, LevelFilter, SetLoggerError};
static CONSOLE_LOGGER: ConsoleLogger = ConsoleLogger;
struct ConsoleLogger;
impl log::Log for ConsoleLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= Level::Info
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
println!("Rust says: {} - {}", record.level(), record.args());
}
}
fn flush(&self) {}
}
fn main() -> Result<(), SetLoggerError> {
log::set_logger(&CONSOLE_LOGGER)?;
log::set_max_level(LevelFilter::Info);
log::info!("hello log");
log::warn!("warning");
log::error!("oops");
Ok(())
}
记录到 Unix 系统日志
本实例实现将信息记录到 UNIX syslog。使用 syslog::init
初始化记录器后端。syslog::Facility
记录提交日志项分类的程序,log::LevelFilter
表示欲记录日志的等级,Option<&str>
定义应用程序名称(可选)。
日志配置
启用每个模块的日志级别
创建两个模块:foo
和其嵌套的 foo::bar
,日志记录指令分别由 RUST_LOG
环境变量控制。
mod foo {
mod bar {
pub fn run() {
log::warn!("[bar] warn");
log::info!("[bar] info");
log::debug!("[bar] debug");
}
}
pub fn run() {
log::warn!("[foo] warn");
log::info!("[foo] info");
log::debug!("[foo] debug");
bar::run();
}
}
fn main() {
env_logger::init();
log::warn!("[root] warn");
log::info!("[root] info");
log::debug!("[root] debug");
foo::run();
}
RUST_LOG
环境变量控制 env_logger
的输出。模块声明采用逗号分隔各项,格式类似于 path::to::module=log_level
。按如下方式运行 test
应用程序:
RUST_LOG="warn,test::foo=info,test::foo::bar=debug" ./test
将日志等级 log::Level
的默认值设置为 warn
,将模块 foo
和其嵌套的模块 foo::bar
的日志等级设置为 info
和 debug
。
WARN:test: [root] warn
WARN:test::foo: [foo] warn
INFO:test::foo: [foo] info
WARN:test::foo::bar: [bar] warn
INFO:test::foo::bar: [bar] info
DEBUG:test::foo::bar: [bar] debug
用自定义环境变量设置日志记录
Builder
配置日志记录。
Builder::parse
以 RUST_LOG
语法的形式解析 MY_APP_LOG
环境变量的内容。然后,Builder::init
初始化记录器。所有这些步骤通常由 env_logger::init
在内部完成。
use std::env;
use env_logger::Builder;
fn main() {
Builder::new()
.parse(&env::var("MY_APP_LOG").unwrap_or_default())
.init();
log::info!("informational message");
log::warn!("warning message");
log::error!("this is an error {}", "message");
}
在日志信息中包含时间戳
使用 Builder
创建自定义记录器配置。每个日志项调用 Local::now
以获取本地时区中的当前 DateTime
,并使用 DateTime::format
和 strftime::specifiers
来格式化最终日志中使用的时间戳。
如下实例调用 Builder::format
设置一个闭包,该闭包用时间戳、Record::level
和正文(Record::args
)对每个信息文本进行格式化。
use std::io::Write;
use chrono::Local;
use env_logger::Builder;
use log::LevelFilter;
fn main() {
Builder::new()
.format(|buf, record| {
writeln!(buf,
"{} [{}] - {}",
Local::now().format("%Y-%m-%dT%H:%M:%S"),
record.level(),
record.args()
)
})
.filter(None, LevelFilter::Info)
.init();
log::warn!("warn");
log::info!("info");
log::debug!("debug");
}
stderr 输入将含有:
2017-05-22T21:57:06 [WARN] - warn
2017-05-22T21:57:06 [INFO] - info
将信息记录到自定义位置
log4rs 将日志输出配置到自定义位置。log4rs 可以使用外部 YAML 文件或生成器配置。
使用文件附加器 log4rs::append::file::FileAppender
创建日志配置,文件附加器定义日志记录的目标位置。日志配置使用 log4rs::encode::pattern
中的自定义模式进行编码,将配置项分配给 log4rs::config::Config
,并设置默认的日志等级 log::LevelFilter
。
版本控制
解析并递增版本字符串
使用 Version::parse
从字符串字面量构造语义化版本 semver::Version
,然后逐个递增补丁(修订)版本号、副(次要)版本号和主版本号。
注意:根据语义化版本控制规范,增加副(次要)版本号时会将补丁(修订)版本号重置为 0,增加主版本号时会将副(次要)版本号和补丁(修订)版本号都重置为 0。
use semver::{Version, SemVerError};
fn main() -> Result<(), SemVerError> {
let mut parsed_version = Version::parse("0.2.6")?;
assert_eq!(
parsed_version,
Version {
major: 0,
minor: 2,
patch: 6,
pre: vec![],
build: vec![],
}
);
parsed_version.increment_patch();
assert_eq!(parsed_version.to_string(), "0.2.7");
println!("New patch release: v{}", parsed_version);
parsed_version.increment_minor();
assert_eq!(parsed_version.to_string(), "0.3.0");
println!("New minor release: v{}", parsed_version);
parsed_version.increment_major();
assert_eq!(parsed_version.to_string(), "1.0.0");
println!("New major release: v{}", parsed_version);
Ok(())
}
解析复杂的版本字符串
使用 Version::parse
从复杂的版本字符串构造语义化版本 semver::Version
。该字符串包含语义化版本控制规范中定义的预发布和构建元数据。
需要注意的是:根据语义化版本控制规范,构建元数据是虽然被解析,但在比较版本时不考虑。换句话说,即使两个版本的构建字符串不同,但它们的版本可能是相等的。
use semver::{Identifier, Version, SemVerError};
fn main() -> Result<(), SemVerError> {
let version_str = "1.0.49-125+g72ee7853";
let parsed_version = Version::parse(version_str)?;
assert_eq!(
parsed_version,
Version {
major: 1,
minor: 0,
patch: 49,
pre: vec![Identifier::Numeric(125)],
build: vec![],
}
);
assert_eq!(
parsed_version.build,
vec![Identifier::AlphaNumeric(String::from("g72ee7853"))]
);
let serialized_version = parsed_version.to_string();
assert_eq!(&serialized_version, version_str);
Ok(())
}
检查给定版本是否为预发布版本
给定两个版本,使用 is_prerelease
断言一个是预发布,另一个不是。
use semver::{Version, SemVerError};
fn main() -> Result<(), SemVerError> {
let version_1 = Version::parse("1.0.0-alpha")?;
let version_2 = Version::parse("1.0.0")?;
assert!(version_1.is_prerelease());
assert!(!version_2.is_prerelease());
Ok(())
}
查询适配给定范围的最新版本
给定一个版本字符串 &str 的列表,查找最新的语义化版本 semver::Version
。semver::VersionReq
用 VersionReq::matches
过滤列表,也可以展示语义化版本 semver
的预发布参数设置。
检查外部命令的版本兼容性
本实例使用 Command
模块运行命令 git --version
,然后使用 Version::parse
将版本号解析为语义化版本 semver::Version
。VersionReq::matches
将 semver::VersionReq
与解析的语义化版本进行比较。最终,命令输出类似于“git version x.y.z”。
构建工具
本节介绍在编译 crate 源代码之前运行的“构建时”工具或代码。按照惯例,构建时代码存放在 build.rs 文件,通常称为“构建脚本”。常见的用例包括:Rust 代码生成、绑定的 C/C++/asm 代码的编译。要获取更多信息,请阅读(Cargo 手册 中文版) 的构建脚本文档。
编译并静态链接到绑定的 C 语言库
为了适应项目中需要混合 C、C++,或 asm 等语言的场景,cc crate 提供了一个简单的 API,用于将绑定的 C/C++/asm 代码编译成静态库(.a),静态库可以通过 rustc 静态链接。
下面的实例有一些绑定的 C 语言代码(src/hello.c),将从 rust 中调用它们。在编译 rust 源代码之前,Cargo.toml 中指定的“构建”文件(build.rs)预先运行。使用 cc crate,将生成一个静态库文件(本实例中为 libhello.a,请参阅 compile
文档),通过在 extern
代码块中声明外部函数签名,然后就可以从 rust 中调用该静态库。
本实例中绑定的 C 语言文件非常简单,只需要将一个源文件传递给 cc::Build
。对于更复杂的构建需求,cc::Build
提供了一整套构建器方法,用于指定(包含)include
路径和扩展编译器标志(flag)
。
Cargo.toml
[package]
...
build = "build.rs"
[build-dependencies]
cc = "1"
[dependencies]
error-chain = "0.11"
build.rs
fn main() {
cc::Build::new()
.file("src/hello.c")
.compile("hello"); // 输出 `libhello.a`
}
src/hello.c
#include <stdio.h>
void hello() {
printf("Hello from C!\n");
}
void greet(const char* name) {
printf("Hello, %s!\n", name);
}
src/main.rs
use error_chain::error_chain;
use std::ffi::CString;
use std::os::raw::c_char;
error_chain! {
foreign_links {
NulError(::std::ffi::NulError);
Io(::std::io::Error);
}
}
fn prompt(s: &str) -> Result<String> {
use std::io::Write;
print!("{}", s);
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
Ok(input.trim().to_string())
}
extern {
fn hello();
fn greet(name: *const c_char);
}
fn main() -> Result<()> {
unsafe { hello() }
let name = prompt("What's your name? ")?;
let c_name = CString::new(name)?;
unsafe { greet(c_name.as_ptr()) }
Ok(())
}
编译并静态链接到绑定的 C++ 语言库
链接绑定的 C++ 语言库非常类似于链接绑定的 C 语言库。编译并静态链接绑定的 C++ 库时,与链接绑定的 C 语言库相比有两个核心区别:一是通过构造器方法 cpp(true)
指定 C++ 编译器;二是通过在 C++ 源文件顶部添加 extern "C"
代码段,以防止 C++ 编译器的名称篡改。
Cargo.toml
[package]
...
build = "build.rs"
[build-dependencies]
cc = "1"
build.rs
fn main() {
cc::Build::new()
.cpp(true)
.file("src/foo.cpp")
.compile("foo");
}
src/foo.cpp
extern "C" {
int multiply(int x, int y);
}
int multiply(int x, int y) {
return x*y;
}
src/main.rs
extern {
fn multiply(x : i32, y : i32) -> i32;
}
fn main(){
unsafe {
println!("{}", multiply(5,7));
}
}
编译 C 语言库时自定义设置
使用 cc::Build::define
自定义构建绑定的 C 语言代码非常简单。该方法接受 Option
值,因此可以创建这样的定义:#define APP_NAME "foo"
、#define WELCOME
(将 None
作为不确定值传递)。如下实例构建了一个绑定的 C 语言文件,其在 build.rs
中设置了动态定义,并在运行时打印 “Welcome to foo - version 1.0.2”。Cargo 设定了一些环境变量,这些变量可能对某些自定义设置有用。
Cargo.toml
[package]
...
version = "1.0.2"
build = "build.rs"
[build-dependencies]
cc = "1"
build.rs
fn main() {
cc::Build::new()
.define("APP_NAME", "\"foo\"")
.define("VERSION", format!("\"{}\"", env!("CARGO_PKG_VERSION")).as_str())
.define("WELCOME", None)
.file("src/foo.c")
.compile("foo");
}
src/foo.c
#include <stdio.h>
void print_app_info() {
#ifdef WELCOME
printf("Welcome to ");
#endif
printf("%s - version %s\n", APP_NAME, VERSION);
}
src/main.rs
extern {
fn print_app_info();
}
fn main(){
unsafe {
print_app_info();
}
}
编码
字符集
实例名称 | Crates | 类别 |
---|---|---|
百分比编码(URL 编码)字符串 | ||
将字符串编码为 application/x-www-form-urlencoded | ||
编码和解码十六进制 | ||
编码和解码 base64 |
CSV 处理
实例名称 | Crates | 类别 |
---|---|---|
读取 CSV 记录 | ||
读取有不同分隔符的 CSV 记录 | ||
筛选匹配断言的 CSV 记录 | ||
用 Serde 处理无效的 CSV 数据 | ||
将记录序列化为 CSV | ||
用 Serde 将记录序列化为 CSV | ||
转换 CSV 文件的列 |
结构化数据
实例名称 | Crates | 类别 |
---|---|---|
对非结构化 JSON 序列化和反序列化 | ||
反序列化 TOML 配置文件 | ||
以小端模式(低位模式)字节顺序读写整数 |
字符集
百分比编码(URL 编码)字符串
使用 percent-encoding
crate 中的 utf8_percent_encode
函数对输入字符串进行百分比编码(URL 编码)。解码使用 percent_decode
函数。
use percent_encoding::{utf8_percent_encode, percent_decode, AsciiSet, CONTROLS};
use std::str::Utf8Error;
/// https://url.spec.whatwg.org/#fragment-percent-encode-set
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
fn main() -> Result<(), Utf8Error> {
let input = "confident, productive systems programming";
let iter = utf8_percent_encode(input, FRAGMENT);
let encoded: String = iter.collect();
assert_eq!(encoded, "confident,%20productive%20systems%20programming");
let iter = percent_decode(encoded.as_bytes());
let decoded = iter.decode_utf8()?;
assert_eq!(decoded, "confident, productive systems programming");
Ok(())
}
编码集定义哪些字节(除了非 ASCII 字节和控制键之外)需要进行百分比编码(URL 编码),这个集合的选择取决于上下文。例如,url
对 URL 路径中的 ?
编码,而不对查询字符串中的 ?
编码。
编码的返回值是 &str
切片的迭代器,然后聚集为一个字符串 String
。
将字符串编码为 application/x-www-form-urlencoded
如下实例使用 form_urlencoded::byte_serialize
将字符串编码为 application/x-www-form-urlencoded 表单语法,随后使用 form_urlencoded::parse
对其进行解码。这两个函数都返回迭代器,然后这些迭代器聚集为 String
。
use url::form_urlencoded::{byte_serialize, parse};
fn main() {
let urlencoded: String = byte_serialize("What is ❤?".as_bytes()).collect();
assert_eq!(urlencoded, "What+is+%E2%9D%A4%3F");
println!("urlencoded:'{}'", urlencoded);
let decoded: String = parse(urlencoded.as_bytes())
.map(|(key, val)| [key, val].concat())
.collect();
assert_eq!(decoded, "What is ❤?");
println!("decoded:'{}'", decoded);
}
编码和解码十六进制
data_encoding
crate 提供了 HEXUPPER::encode
方法,该方法接受 &[u8]
参数并返回十六进制数据的字符串 String
。
类似地,data_encoding
crate 提供了 HEXUPPER::decode
方法,该方法接受 &[u8]
参数。如果输入数据被成功解码,则返回 Vec<u8>
。
下面的实例将 &[u8]
数据转换为等效的十六进制数据,然后将此值与预期值进行比较。
use data_encoding::{HEXUPPER, DecodeError};
fn main() -> Result<(), DecodeError> {
let original = b"The quick brown fox jumps over the lazy dog.";
let expected = "54686520717569636B2062726F776E20666F78206A756D7073206F76\
657220746865206C617A7920646F672E";
let encoded = HEXUPPER.encode(original);
assert_eq!(encoded, expected);
let decoded = HEXUPPER.decode(&encoded.into_bytes())?;
assert_eq!(&decoded[..], &original[..]);
Ok(())
}
编码和解码 base64
使用 encode
将字节切片编码为 base64
字符串,对 base64
字符串解码使用 decode
。
CSV 处理
读取 CSV 记录
将标准的 CSV 记录读入 csv::StringRecord
——一种弱类型的数据表示方式,它需要 CSV 中的行数据是有效的 UTF-8 字符编码。另外,csv::ByteRecord
对 UTF-8 不做任何预设。
use csv::Error;
fn main() -> Result<(), Error> {
let csv = "year,make,model,description
1948,Porsche,356,Luxury sports car
1967,Ford,Mustang fastback 1967,American car";
let mut reader = csv::Reader::from_reader(csv.as_bytes());
for record in reader.records() {
let record = record?;
println!(
"In {}, {} built the {} model. It is a {}.",
&record[0],
&record[1],
&record[2],
&record[3]
);
}
Ok(())
}
Serde 将数据反序列化为强类型结构体。具体查阅 csv::Reader::deserialize
方法。
use serde::Deserialize;
#[derive(Deserialize)]
struct Record {
year: u16,
make: String,
model: String,
description: String,
}
fn main() -> Result<(), csv::Error> {
let csv = "year,make,model,description
1948,Porsche,356,Luxury sports car
1967,Ford,Mustang fastback 1967,American car";
let mut reader = csv::Reader::from_reader(csv.as_bytes());
for record in reader.deserialize() {
let record: Record = record?;
println!(
"In {}, {} built the {} model. It is a {}.",
record.year,
record.make,
record.model,
record.description
);
}
Ok(())
}
读取有不同分隔符的 CSV 记录
使用制表(tab)分隔符 delimiter
读取 CSV 记录。
use csv::Error;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Record {
name: String,
place: String,
#[serde(deserialize_with = "csv::invalid_option")]
id: Option<u64>,
}
use csv::ReaderBuilder;
fn main() -> Result<(), Error> {
let data = "name\tplace\tid
Mark\tMelbourne\t46
Ashley\tZurich\t92";
let mut reader = ReaderBuilder::new().delimiter(b'\t').from_reader(data.as_bytes());
for result in reader.deserialize::<Record>() {
println!("{:?}", result?);
}
Ok(())
}
筛选匹配断言的 CSV 记录
仅仅 返回 data
中字段(field)与 query
匹配的的行。
免责声明:此实例改编自csv crate 教程。
用 Serde 处理无效的 CSV 数据
CSV 文件通常包含无效数据。对于这些情形,csv
crate 提供了一个自定义的反序列化程序 csv::invalid_option
,它自动将无效数据转换为 None 值。
use csv::Error;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Record {
name: String,
place: String,
#[serde(deserialize_with = "csv::invalid_option")]
id: Option<u64>,
}
fn main() -> Result<(), Error> {
let data = "name,place,id
mark,sydney,46.5
ashley,zurich,92
akshat,delhi,37
alisha,colombo,xyz";
let mut rdr = csv::Reader::from_reader(data.as_bytes());
for result in rdr.deserialize() {
let record: Record = result?;
println!("{:?}", record);
}
Ok(())
}
将记录序列化为 CSV
本实例展示了如何序列化 Rust 元组。csv::writer
支持从 Rust 类型到 CSV 记录的自动序列化。write_record
只写入包含字符串数据的简单记录。具有更复杂值(如数字、浮点和选项)的数据使用 serialize
进行序列化。因为 csv::writer
使用内部缓冲区,所以在完成时总是显式刷新 flush
。
用 Serde 将记录序列化为 CSV
下面的实例展示如何使用 serde crate 将自定义结构体序列化为 CSV 记录。
转换 CSV 文件的列
将包含颜色名称和十六进制颜色值的 CSV 文件转换为具有颜色名称和 rgb 颜色值的 CSV 文件。使用 csv crate 读写 csv 文件,使用 serde crate 对行输入字节进行反序列化,对行输出字节进行序列化。
详细请参阅 csv::Reader::deserialize
、serde::Deserialize
,以及 std::str::FromStr
。
结构化数据
对非结构化 JSON 序列化和反序列化
serde_json
crate 提供了 from_str
函数来解析 JSON 切片 &str
。
非结构化 JSON 可以被解析为一个通用的 serde_json::Value
类型,该类型能够表示任何有效的 JSON 数据。
下面的实例展示如何解析 JSON 切片 &str
,期望值被 json!
宏声明。
use serde_json::json;
use serde_json::{Value, Error};
fn main() -> Result<(), Error> {
let j = r#"{
"userid": 103609,
"verified": true,
"access_privileges": [
"user",
"admin"
]
}"#;
let parsed: Value = serde_json::from_str(j)?;
let expected = json!({
"userid": 103609,
"verified": true,
"access_privileges": [
"user",
"admin"
]
});
assert_eq!(parsed, expected);
Ok(())
}
反序列化 TOML 配置文件
将一些 TOML 配置项解析为一个通用的值 toml::Value
,该值能够表示任何有效的 TOML 数据。
use toml::{Value, de::Error};
fn main() -> Result<(), Error> {
let toml_content = r#"
[package]
name = "your_package"
version = "0.1.0"
authors = ["You! <you@example.org>"]
[dependencies]
serde = "1.0"
"#;
let package_info: Value = toml::from_str(toml_content)?;
assert_eq!(package_info["dependencies"]["serde"].as_str(), Some("1.0"));
assert_eq!(package_info["package"]["name"].as_str(),
Some("your_package"));
Ok(())
}
使用 Serde crate 将 TOML 解析为自定义的结构体。
use serde::Deserialize;
use toml::de::Error;
use std::collections::HashMap;
#[derive(Deserialize)]
struct Config {
package: Package,
dependencies: HashMap<String, String>,
}
#[derive(Deserialize)]
struct Package {
name: String,
version: String,
authors: Vec<String>,
}
fn main() -> Result<(), Error> {
let toml_content = r#"
[package]
name = "your_package"
version = "0.1.0"
authors = ["You! <you@example.org>"]
[dependencies]
serde = "1.0"
"#;
let package_info: Config = toml::from_str(toml_content)?;
assert_eq!(package_info.package.name, "your_package");
assert_eq!(package_info.package.version, "0.1.0");
assert_eq!(package_info.package.authors, vec!["You! <you@example.org>"]);
assert_eq!(package_info.dependencies["serde"], "1.0");
Ok(())
}
以小端模式(低位模式)字节顺序读写整数
字节序 byteorder
可以反转结构化数据的有效字节。当通过网络接收信息时,这可能是必要的,例如接收到的字节来自另一个系统。
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::Error;
#[derive(Default, PartialEq, Debug)]
struct Payload {
kind: u8,
value: u16,
}
fn main() -> Result<(), Error> {
let original_payload = Payload::default();
let encoded_bytes = encode(&original_payload)?;
let decoded_payload = decode(&encoded_bytes)?;
assert_eq!(original_payload, decoded_payload);
Ok(())
}
fn encode(payload: &Payload) -> Result<Vec<u8>, Error> {
let mut bytes = vec![];
bytes.write_u8(payload.kind)?;
bytes.write_u16::<LittleEndian>(payload.value)?;
Ok(bytes)
}
fn decode(mut bytes: &[u8]) -> Result<Payload, Error> {
let payload = Payload {
kind: bytes.read_u8()?,
value: bytes.read_u16::<LittleEndian>()?,
};
Ok(payload)
}
错误处理
处理错误变量
实例名称 | Crates | 类别 |
---|---|---|
在 main 方法中对错误适当处理 | ||
避免在错误转变过程中遗漏错误 | ||
获取复杂错误场景的回溯 |
处理错误变量
在 main 方法中对错误适当处理
处理尝试打开不存在的文件时发生的错误,是通过使用 error-chain crate 来实现的。error-chain crate 包含大量的模板代码,用于 Rust 中的错误处理。
foreign_links
代码块内的 Io(std::io::Error)
函数允许由 std::io::Error
所报错误信息到 error_chain!
所定义错误类型的自动转换,error_chain!
所定义错误类型将实现 Error
trait。
下文的实例将通过打开 Unix 文件 /proc/uptime
并解析内容以获得其中第一个数字,从而告诉系统运行了多长时间。除非出现错误,否则返回正常运行时间。
本书中的其他实例将隐藏 error-chain 模板,如果需要查看,可以通过 ⤢ 按钮展开代码。
use error_chain::error_chain;
use std::fs::File;
use std::io::Read;
error_chain!{
foreign_links {
Io(std::io::Error);
ParseInt(::std::num::ParseIntError);
}
}
fn read_uptime() -> Result<u64> {
let mut uptime = String::new();
File::open("/proc/uptime")?.read_to_string(&mut uptime)?;
Ok(uptime
.split('.')
.next()
.ok_or("Cannot parse uptime data")?
.parse()?)
}
fn main() {
match read_uptime() {
Ok(uptime) => println!("uptime: {} seconds", uptime),
Err(err) => eprintln!("error: {}", err),
};
}
避免在错误转变过程中遗漏错误
error-chain crate 使得匹配函数返回的不同错误类型成为可能,并且相对简洁。ErrorKind
是枚举类型,可以确定错误类型。
下文实例使用 reqwest::blocking 来查询一个随机整数生成器的 web 服务,并将服务器响应的字符串转换为整数。Rust 标准库 reqwest 和 web 服务都可能会产生错误,所以使用 foreign_links
定义易于辨认理解的 Rust 错误。另外,用于 web 服务错误信息的 ErrorKind
变量,使用 error_chain!
宏的 errors
代码块定义。
use error_chain::error_chain;
error_chain! {
foreign_links {
Io(std::io::Error);
Reqwest(reqwest::Error);
ParseIntError(std::num::ParseIntError);
}
errors { RandomResponseError(t: String) }
}
fn parse_response(response: reqwest::blocking::Response) -> Result<u32> {
let mut body = response.text()?;
body.pop();
body
.parse::<u32>()
.chain_err(|| ErrorKind::RandomResponseError(body))
}
fn run() -> Result<()> {
let url =
format!("https://www.random.org/integers/?num=1&min=0&max=10&col=1&base=10&format=plain");
let response = reqwest::blocking::get(&url)?;
let random_value: u32 = parse_response(response)?;
println!("a random number between 0 and 10: {}", random_value);
Ok(())
}
fn main() {
if let Err(error) = run() {
match *error.kind() {
ErrorKind::Io(_) => println!("Standard IO error: {:?}", error),
ErrorKind::Reqwest(_) => println!("Reqwest error: {:?}", error),
ErrorKind::ParseIntError(_) => println!("Standard parse int error: {:?}", error),
ErrorKind::RandomResponseError(_) => println!("User defined error: {:?}", error),
_ => println!("Other error: {:?}", error),
}
}
}
获取复杂错误场景的回溯
本实例展示了如何处理一个复杂的错误场景,并且打印出错误回溯。依赖于 chain_err
,通过附加新的错误来扩展错误信息。从而可以展开错误堆栈,这样提供了更好的上下文来理解错误的产生原因。
下述代码尝试将值 256
反序列化为 u8
。首先 Serde 产生错误,然后是 csv,最后是用户代码。
错误回溯信息如下:
Error level - description
└> 0 - Cannot read CSV data
└> 1 - Cannot deserialize RGB color
└> 2 - CSV deserialize error: record 1 (line: 2, byte: 15): field 1: number too large to fit in target type
└> 3 - field 1: number too large to fit in target type
可以通过附加命令参数 RUST_BACKTRACE=1
运行实例,以显示与此错误相关的详细回溯。
文件系统
文件读写
实例名称 | Crates | 类别 |
---|---|---|
读取文件的字符串行 | ||
避免读取写入同一文件 | ||
使用内存映射随机访问文件 |
目录遍历
实例名称 | Crates | 类别 |
---|---|---|
过去 24 小时内修改过的文件名 | ||
查找给定路径的循环 | ||
递归查找重名文件 | ||
使用给定断言递归查找所有文件 | ||
跳过隐藏文件遍历目录 | ||
在给定深度的目录,递归计算文件大小 | ||
递归查找所有 png 文件 | ||
忽略文件名大小写,使用给定模式查找所有文件 |
文件读写
读取文件的字符串行
我们向文件写入三行信息,然后使用 BufRead::lines
创建的迭代器 Lines
读取文件,一次读回一行。File
模块实现了提供 BufReader
结构体的 Read
trait。File::create
打开文件 File
进行写入,File::open
则进行读取。
use std::fs::File;
use std::io::{Write, BufReader, BufRead, Error};
fn main() -> Result<(), Error> {
let path = "lines.txt";
let mut output = File::create(path)?;
write!(output, "Rust\n💖\nFun")?;
let input = File::open(path)?;
let buffered = BufReader::new(input);
for line in buffered.lines() {
println!("{}", line?);
}
Ok(())
}
避免读取写入同一文件
对文件使用 same_file::Handle
结构体,可以测试文件句柄是否等同。在本实例中,将对要读取和写入的文件句柄进行相等性测试。
use same_file::Handle;
use std::fs::File;
use std::io::{BufRead, BufReader, Error, ErrorKind};
use std::path::Path;
fn main() -> Result<(), Error> {
let path_to_read = Path::new("new.txt");
let stdout_handle = Handle::stdout()?;
let handle = Handle::from_path(path_to_read)?;
if stdout_handle == handle {
return Err(Error::new(
ErrorKind::Other,
"You are reading and writing to the same file",
));
} else {
let file = File::open(&path_to_read)?;
let file = BufReader::new(file);
for (num, line) in file.lines().enumerate() {
println!("{} : {}", num, line?.to_uppercase());
}
}
Ok(())
}
cargo run
显示文件 new.txt 的内容。
cargo run >> ./new.txt
报错,因为是同一文件。
使用内存映射随机访问文件
使用 memmap 创建文件的内存映射,并模拟文件的一些非序列读取。使用内存映射意味着您仅需索引一个切片,而不是使用 seek
方法来导航整个文件。
Mmap::map
函数假定内存映射后的文件没有被另一个进程同时更改,否则会出现竞态条件。
目录遍历
过去 24 小时内修改过的文件名
通过调用 env::current_dir
获取当前工作目录,然后通过 fs::read_dir
读取目录中的每个条目,通过 DirEntry::path
提取条目路径,以及通过通过 fs::Metadata
获取条目元数据。Metadata::modified
返回条目自上次更改以来的运行时间 SystemTime::elapsed
。Duration::as_secs
将时间转换为秒,并与 24 小时(24 * 60 * 60 秒)进行比较。Metadata::is_file
用于筛选出目录。
查找给定路径的循环
使用 same_file::is_same_file
检测给定路径的循环。例如,可以通过软连接(符号链接)在 Unix 系统上创建循环:
mkdir -p /tmp/foo/bar/baz
ln -s /tmp/foo/ /tmp/foo/bar/baz/qux
下面的实例将断言存在一个循环。
use std::io;
use std::path::{Path, PathBuf};
use same_file::is_same_file;
fn contains_loop<P: AsRef<Path>>(path: P) -> io::Result<Option<(PathBuf, PathBuf)>> {
let path = path.as_ref();
let mut path_buf = path.to_path_buf();
while path_buf.pop() {
if is_same_file(&path_buf, path)? {
return Ok(Some((path_buf, path.to_path_buf())));
} else if let Some(looped_paths) = contains_loop(&path_buf)? {
return Ok(Some(looped_paths));
}
}
return Ok(None);
}
fn main() {
assert_eq!(
contains_loop("/tmp/foo/bar/baz/qux/bar/baz").unwrap(),
Some((
PathBuf::from("/tmp/foo"),
PathBuf::from("/tmp/foo/bar/baz/qux")
))
);
}
递归查找重名文件
在当前目录中递归查找重复的文件名,只打印一次。
use std::collections::HashMap;
use walkdir::WalkDir;
fn main() {
let mut filenames = HashMap::new();
for entry in WalkDir::new(".")
.into_iter()
.filter_map(Result::ok)
.filter(|e| !e.file_type().is_dir()) {
let f_name = String::from(entry.file_name().to_string_lossy());
let counter = filenames.entry(f_name.clone()).or_insert(0);
*counter += 1;
if *counter == 2 {
println!("{}", f_name);
}
}
}
使用给定断言递归查找所有文件
在当前目录中查找最近一天内修改的 JSON 文件。使用 follow_links
确保软链接(符号链接)像普通目录和文件一样被按照当前查找规则执行。
跳过隐藏文件遍历目录
递归下行到子目录的过程中,使用 filter_entry
对目录中的条目传递 is_not_hidden
断言,从而跳过隐藏的文件和目录。Iterator::filter
可应用到要检索的任何目录 WalkDir::DirEntry
,即使父目录是隐藏目录。
根目录 "."
的检索结果,通过在断言 is_not_hidden
中使用 WalkDir::depth
参数生成。
use walkdir::{DirEntry, WalkDir};
fn is_not_hidden(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| entry.depth() == 0 || !s.starts_with("."))
.unwrap_or(false)
}
fn main() {
WalkDir::new(".")
.into_iter()
.filter_entry(|e| is_not_hidden(e))
.filter_map(|v| v.ok())
.for_each(|x| println!("{}", x.path().display()));
}
在给定深度的目录,递归计算文件大小
通过 WalkDir::min_depth
和 WalkDir::max_depth
方法,可以灵活设置目录的递归深度。下面的实例计算了 3 层子文件夹深度的所有文件的大小总和,计算中忽略根文件夹中的文件。
use walkdir::WalkDir;
fn main() {
let total_size = WalkDir::new(".")
.min_depth(1)
.max_depth(3)
.into_iter()
.filter_map(|entry| entry.ok())
.filter_map(|entry| entry.metadata().ok())
.filter(|metadata| metadata.is_file())
.fold(0, |acc, m| acc + m.len());
println!("Total size: {} bytes.", total_size);
}
递归查找所有 png 文件
递归地查找当前目录中的所有 PNG 文件。在本实例中,**
模式用于匹配当前目录及其所有子目录。
在路径任意部分使用 **
模式,例如,/media/**/*.png
匹配 media
及其子目录中的所有 PNG 文件。
忽略文件名大小写,使用给定模式查找所有文件
在 /media/
目录中查找与正则表达模式 img_[0-9]*.png
匹配的所有图像文件。
一个自定义 MatchOptions
结构体被传递给 glob_with
函数,使全局命令模式下不区分大小写,同时保持其他选项的默认值 Default
。
译者注:
glob
是glob command
的简写。在 shell 里面,用*
等匹配模式来匹配文件,如:ls src/*.rs。
use error_chain::error_chain;
use glob::{glob_with, MatchOptions};
error_chain! {
foreign_links {
Glob(glob::GlobError);
Pattern(glob::PatternError);
}
}
fn main() -> Result<()> {
let options = MatchOptions {
case_sensitive: false,
..Default::default()
};
for entry in glob_with("/media/img_[0-9]*.png", options)? {
println!("{}", entry?.display());
}
Ok(())
}
硬件支持
处理器
实例名称 | Crates | 类别 |
---|---|---|
检查逻辑 cpu 内核的数量 |
处理器
检查逻辑 cpu 内核的数量
使用 [num_cpus::get
] 显示当前机器中的逻辑 CPU 内核的数量。
fn main() {
println!("Number of logical cores is {}", num_cpus::get());
}
内存管理
常量
实例名称 | Crates | 类别 |
---|---|---|
声明延迟计算常量 |
常量
声明延迟计算常量
声明延迟计算的常量 HashMap
。HashMap
将被计算一次,随后存储在全局静态(全局堆栈)引用。
use lazy_static::lazy_static;
use std::collections::HashMap;
lazy_static! {
static ref PRIVILEGES: HashMap<&'static str, Vec<&'static str>> = {
let mut map = HashMap::new();
map.insert("James", vec!["user", "admin"]);
map.insert("Jim", vec!["user"]);
map
};
}
fn show_access(name: &str) {
let access = PRIVILEGES.get(name);
println!("{}: {:?}", name, access);
}
fn main() {
let access = PRIVILEGES.get("James");
println!("James: {:?}", access);
show_access("Jim");
}
网络
服务器
实例名称 | Crates | 类别 |
---|---|---|
监听未使用的 TCP/IP 端口 |
服务器
监听未使用的 TCP/IP 端口
在本实例中,程序将监听显示在控制台上的端口,直到一个请求被发出。当将端口设置为 0 时,SocketAddrV4
会分配一个随机端口。
use std::net::{SocketAddrV4, Ipv4Addr, TcpListener};
use std::io::{Read, Error};
fn main() -> Result<(), Error> {
let loopback = Ipv4Addr::new(127, 0, 0, 1);
let socket = SocketAddrV4::new(loopback, 0);
let listener = TcpListener::bind(socket)?;
let port = listener.local_addr()?;
println!("Listening on {}, access this port to end the program", port);
let (mut tcp_stream, addr) = listener.accept()?; // 阻塞,直到被请求
println!("Connection received! {:?} is sending data.", addr);
let mut input = String::new();
let _ = tcp_stream.read_to_string(&mut input)?;
println!("{:?} says {}", addr, input);
Ok(())
}
操作系统
外部命令
实例名称 | Crates | 类别 |
---|---|---|
运行外部命令并处理 stdout | ||
运行传递 stdin 的外部命令,并检查错误代码 | ||
运行管道传输的外部命令 | ||
将子进程的 stdout 和 stderr 重定向到同一个文件 | ||
持续处理子进程的输出 | ||
读取环境变量 |
外部命令
运行外部命令并处理 stdout
将 git log --oneline
作为外部命令 Command
运行,并使用 Regex
检查其 Output
,以获取最后 5 次提交的哈希值和消息。
运行传递 stdin 的外部命令,并检查错误代码
使用外部命令 Command
打开 python
解释器,并传递一条 python 语句供其执行,然后解析语句的输出结构体 Output
。
运行管道传输的外部命令
显示当前工作目录中前 10 大的文件和子目录,它等同于运行: du -ah . | sort -hr | head -n 10
。
每个命令 Command
代表一个进程,子进程的输出是通过父进程和子进程之间的管道 Stdio::piped
捕获的。
将子进程的 stdout 和 stderr 重定向到同一个文件
生成子进程并将 stdout
和 stderr
重定向到同一个文件。它遵循与运行管道传输的外部命令相同的思想,但是 process::Stdio
会将输出写入指定的文件。对 stdout
和 stderr
而言,File::try_clone
引用相同的文件句柄。它将确保两个句柄使用相同的光标位置进行写入。
下面的实例等同于运行 Unix shell 命令 ls . oops >out.txt 2>&1
。
use std::fs::File;
use std::io::Error;
use std::process::{Command, Stdio};
fn main() -> Result<(), Error> {
let outputs = File::create("out.txt")?;
let errors = outputs.try_clone()?;
Command::new("ls")
.args(&[".", "oops"])
.stdout(Stdio::from(outputs))
.stderr(Stdio::from(errors))
.spawn()?
.wait_with_output()?;
Ok(())
}
持续处理子进程的输出
在运行外部命令并处理-stdout 实例中,直到外部命令 Command
完成,stdout 的处理才开始。下面的实例调用 Stdio::piped
创建管道,并在 BufReader
被更新后立即读取 stdout
,持续不断地处理。
下面的实例等同于 Unix shell 命令 journalctl | grep usb
。
use std::process::{Command, Stdio};
use std::io::{BufRead, BufReader, Error, ErrorKind};
fn main() -> Result<(), Error> {
let stdout = Command::new("journalctl")
.stdout(Stdio::piped())
.spawn()?
.stdout
.ok_or_else(|| Error::new(ErrorKind::Other,"Could not capture standard output."))?;
let reader = BufReader::new(stdout);
reader
.lines()
.filter_map(|line| line.ok())
.filter(|line| line.find("usb").is_some())
.for_each(|line| println!("{}", line));
Ok(())
}
读取环境变量
通过 std::env::var 读取环境变量。
use std::env;
use std::fs;
use std::io::Error;
fn main() -> Result<(), Error> {
// 从环境变量 `CONFIG` 读取配置路径 `config_path`。
// 如果 `CONFIG` 未设置,采用默认配置路径。
let config_path = env::var("CONFIG")
.unwrap_or("/etc/myapp/config".to_string());
let config: String = fs::read_to_string(config_path)?;
println!("Config: {}", config);
Ok(())
}
科学计算
数学
线性代数
实例名称 | Crates | 类别 |
---|---|---|
Vector 范数 | ||
Vector 比较 | ||
矩阵相加 | ||
矩阵相乘 | ||
标量、vector、矩阵相乘 | ||
矩阵求逆 | ||
(反)序列化矩阵 |
三角学
实例名称 | Crates | 类别 |
---|---|---|
计算三角形的边长 | ||
验证正切(tan)等于正弦(sin)除以余弦(cos) | ||
地球上两点之间的距离 |
复数
实例名称 | Crates | 类别 |
---|---|---|
创建复数 | ||
复数相加 | ||
复数的数学函数 |
统计学
实例名称 | Crates | 类别 |
---|---|---|
集中趋势度量 | ||
计算标准偏差 |
其它数学计算
实例名称 | Crates | 类别 |
---|---|---|
大数 |
数学
线性代数
实例名称 | Crates | 类别 |
---|---|---|
Vector 范数 | ||
Vector 比较 | ||
矩阵相加 | ||
矩阵相乘 | ||
标量、vector、矩阵相乘 | ||
矩阵求逆 | ||
(反)序列化矩阵 |
三角学
实例名称 | Crates | 类别 |
---|---|---|
计算三角形的边长 | ||
验证正切(tan)等于正弦(sin)除以余弦(cos) | ||
地球上两点之间的距离 |
复数
实例名称 | Crates | 类别 |
---|---|---|
创建复数 | ||
复数相加 | ||
复数的数学函数 |
统计学
实例名称 | Crates | 类别 |
---|---|---|
集中趋势度量 | ||
计算标准偏差 |
其它数学计算
实例名称 | Crates | 类别 |
---|---|---|
大数 |
线性代数
矩阵相加
使用 ndarray::arr2
创建两个二维(2-D)矩阵,并按元素方式求和。
注意:sum 的计算方式为 let sum = &a + &b
,借用 &
运算符获得 a
和 b
的引用,可避免销毁他们,使它们可以稍后显示。这样,就创建了一个包含其和的新数组。
use ndarray::arr2;
fn main() {
let a = arr2(&[[1, 2, 3],
[4, 5, 6]]);
let b = arr2(&[[6, 5, 4],
[3, 2, 1]]);
let sum = &a + &b;
println!("{}", a);
println!("+");
println!("{}", b);
println!("=");
println!("{}", sum);
}
矩阵相乘
使用 ndarray::arr2
创建两个矩阵,并使用 ndarray::ArrayBase::dot
对它们执行矩阵乘法。
use ndarray::arr2;
fn main() {
let a = arr2(&[[1, 2, 3],
[4, 5, 6]]);
let b = arr2(&[[6, 3],
[5, 2],
[4, 1]]);
println!("{}", a.dot(&b));
}
标量、vector、矩阵相乘
使用 ndarray::arr1
创建一维(1-D)数组(vector),使用 ndarray::arr2
创建二维(2-D)数组(矩阵)。
首先,一个标量乘以一个 vector 得到另一个 vector。然后,使用 ndarray::Array2::dot
将矩阵乘以新的 vector(矩阵相乘使用 dot
函数,而 *
运算符执行元素方式的乘法)。
在 ndarray
crate 中,根据上下文,一维数组可以解释为行 vector 或列 vector。如果 vector 表示的方向很重要,则必须使用只有一行或一列的二维(2-D)数组。在本实例中,vector 是右侧的一维(1-D)数组,因此 dot
函数将其处理为列 vector。
use ndarray::{arr1, arr2, Array1};
fn main() {
let scalar = 4;
let vector = arr1(&[1, 2, 3]);
let matrix = arr2(&[[4, 5, 6],
[7, 8, 9]]);
let new_vector: Array1<_> = scalar * vector;
println!("{}", new_vector);
let new_matrix = matrix.dot(&new_vector);
println!("{}", new_matrix);
}
Vector 比较
ndarray crate 支持多种创建数组的方法——此实例使用 from
从 std::Vec
创建数组 ndarray::Array
。然后,对数组以元素方式求和。
下面的实例按元素方式比较两个浮点型 vector。浮点数的存储通常不精确,因此很难进行精确的比较。但是,approx
crate 中的 assert_abs_diff_eq!
宏允许方便地比较浮点型元素。要将 approx
和 ndarray
两个 crate一起使用,必须在 Cargo.toml
文件中的 ndarray
依赖项添加 approx
特性。例如:ndarray = { version = "0.13", features = ["approx"] }
。
此实例还包含其他所有权示例。在这里,let z = a + b
执行后,会销毁 a
and b
,然后所有权会转移到 z
。或者,let w = &c + &d
创建一个新的 vector,而不销毁 c
或者 d
,允许以后对它们进行修改。有关其他详细信息,请参见带有两个数组的二进制运算符。
use approx::assert_abs_diff_eq;
use ndarray::Array;
fn main() {
let a = Array::from(vec![1., 2., 3., 4., 5.]);
let b = Array::from(vec![5., 4., 3., 2., 1.]);
let mut c = Array::from(vec![1., 2., 3., 4., 5.]);
let mut d = Array::from(vec![5., 4., 3., 2., 1.]);
let z = a + b;
let w = &c + &d;
assert_abs_diff_eq!(z, Array::from(vec![6., 6., 6., 6., 6.]));
println!("c = {}", c);
c[0] = 10.;
d[1] = 10.;
assert_abs_diff_eq!(w, Array::from(vec![6., 6., 6., 6., 6.]));
}
Vector 范数
这个实例展示了 Array1
类型、ArrayView1
类型、fold
方法,以及 dot
方法在计算给定 vector 的 l1 和 l2 范数时的用法。
+ l2_norm
函数是两者中较简单的,它计算一个 vector 与自身的点积(dot product,数量积)的平方根。
+ l1_norm
函数通过 fold
运算来计算元素的绝对值(也可以通过 x.mapv(f64::abs).scalar_sum()
执行,但是会为 mapv
的结果分配一个新的数组)。
请注意:l1_norm
和 l2_norm
都采用 ArrayView1
类型。这个实例考虑了 vector 范数,所以范数函数只需要接受一维视图(ArrayView1
)。虽然函数可以使用类型为 &Array1<f64>
的参数,但这将要求调用方引用拥有所有权的数组,这比访问视图更为严格(因为视图可以从任意数组或视图创建,而不仅仅是从拥有所有权的数组创建)。
Array
和 ArrayView
都是 ArrayBase
的类型别名。于是,大多数的调用方参数类型可以是 &ArrayBase<S, Ix1> where S: Data
,这样调用方就可以使用 &array
或者 &view
而不是 x.view()
。如果该函数是公共 API 的一部分,那么对于用户来说,这可能是一个更好的选择。对于内部函数,更简明的 ArrayView1<f64>
或许更合适。
use ndarray::{array, Array1, ArrayView1};
fn l1_norm(x: ArrayView1<f64>) -> f64 {
x.fold(0., |acc, elem| acc + elem.abs())
}
fn l2_norm(x: ArrayView1<f64>) -> f64 {
x.dot(&x).sqrt()
}
fn normalize(mut x: Array1<f64>) -> Array1<f64> {
let norm = l2_norm(x.view());
x.mapv_inplace(|e| e/norm);
x
}
fn main() {
let x = array![1., 2., 3., 4., 5.];
println!("||x||_2 = {}", l2_norm(x.view()));
println!("||x||_1 = {}", l1_norm(x.view()));
println!("Normalizing x yields {:?}", normalize(x));
}
矩阵求逆
用 nalgebra::Matrix3
创建一个 3x3 的矩阵,如果可能的话,将其求逆。
use nalgebra::Matrix3;
fn main() {
let m1 = Matrix3::new(2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 2.0, 1.0, 2.0);
println!("m1 = {}", m1);
match m1.try_inverse() {
Some(inv) => {
println!("The inverse of m1 is: {}", inv);
}
None => {
println!("m1 is not invertible!");
}
}
}
(反)序列化矩阵
本实例实现将矩阵序列化为 JSON,以及从 JSON 反序列化出矩阵。序列化由 serde_json::to_string
处理,serde_json::from_str
则执行反序列化。
请注意:序列化后再反序列化将返回原始矩阵。
extern crate nalgebra;
extern crate serde_json;
use nalgebra::DMatrix;
fn main() -> Result<(), std::io::Error> {
let row_slice: Vec<i32> = (1..5001).collect();
let matrix = DMatrix::from_row_slice(50, 100, &row_slice);
// 序列化矩阵
let serialized_matrix = serde_json::to_string(&matrix)?;
// 反序列化出矩阵
let deserialized_matrix: DMatrix<i32> = serde_json::from_str(&serialized_matrix)?;
// 验证反序列化出的矩阵 `deserialized_matrix` 等同于原始矩阵 `matrix`
assert!(deserialized_matrix == matrix);
Ok(())
}
三角学
计算三角形的边长
计算直角三角形斜边的长度,其中斜边的角度为 2 弧度,对边长度为 80。
fn main() {
let angle: f64 = 2.0;
let side_length = 80.0;
let hypotenuse = side_length / angle.sin();
println!("Hypotenuse: {}", hypotenuse);
}
验证正切(tan)等于正弦(sin)除以余弦(cos)
验证 tan(x) 是否等于 sin(x)/cos(x),其中 x=6。
fn main() {
let x: f64 = 6.0;
let a = x.tan();
let b = x.sin() / x.cos();
assert_eq!(a, b);
}
地球上两点之间的距离
默认情况下,Rust 提供了数学上的浮点数方法,例如:三角函数、平方根、弧度和度数之间的转换函数等。
下面的实例使用半正矢公式计算地球上两点之间的距离(以公里为单位)。两个点用一对经纬度表示,然后,to_radians
将它们转换为弧度。sin
、cos
、powi
以及 sqrt
计算中心角。最终,可以计算出距离。
fn main() {
let earth_radius_kilometer = 6371.0_f64;
let (paris_latitude_degrees, paris_longitude_degrees) = (48.85341_f64, -2.34880_f64);
let (london_latitude_degrees, london_longitude_degrees) = (51.50853_f64, -0.12574_f64);
let paris_latitude = paris_latitude_degrees.to_radians();
let london_latitude = london_latitude_degrees.to_radians();
let delta_latitude = (paris_latitude_degrees - london_latitude_degrees).to_radians();
let delta_longitude = (paris_longitude_degrees - london_longitude_degrees).to_radians();
let central_angle_inner = (delta_latitude / 2.0).sin().powi(2)
+ paris_latitude.cos() * london_latitude.cos() * (delta_longitude / 2.0).sin().powi(2);
let central_angle = 2.0 * central_angle_inner.sqrt().asin();
let distance = earth_radius_kilometer * central_angle;
println!(
"Distance between Paris and London on the surface of Earth is {:.1} kilometers",
distance
);
}
复数
创建复数
创建类型 num::complex::Complex
的复数,复数的实部和虚部必须是同一类型。
fn main() {
let complex_integer = num::complex::Complex::new(10, 20);
let complex_float = num::complex::Complex::new(10.1, 20.1);
println!("Complex integer: {}", complex_integer);
println!("Complex float: {}", complex_float);
}
复数相加
对复数执行数学运算与对内置类型执行数学运算是一样的:计算的数字必须是相同的类型(如浮点数或整数)。
fn main() {
let complex_num1 = num::complex::Complex::new(10.0, 20.0); // 必须为浮点数
let complex_num2 = num::complex::Complex::new(3.1, -4.2);
let sum = complex_num1 + complex_num2;
println!("Sum: {}", sum);
}
复数的数学函数
在与其他数学函数交互时,复数有一系列有趣的特性,尤其是和自然常数 e(欧拉数)类似的正弦相关函数。要将这些函数与复数一起使用,复数类型有几个内置函数,详细请参阅 num::complex::Complex
。
use std::f64::consts::PI;
use num::complex::Complex;
fn main() {
let x = Complex::new(0.0, 2.0*PI);
println!("e^(2i * pi) = {}", x.exp()); // =~1
}
统计学
集中趋势度量
本节实例计算 Rust 数组中包含的数据集的集中趋势度量。对于一个空的数据集,可能没有平均数、中位数或众数去计算,因此每个函数都返回 [Option
] ,由调用者处理。
第一个实例是通过对数据引用生成一个迭代器,然后计算平均数(所有测量值的总和除以测量值的计数),并使用 [sum
] 和 [len
] 函数分别确定值的总和及值的计数。
fn main() {
let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
let sum = data.iter().sum::<i32>() as f32;
let count = data.len();
let mean = match count {
positive if positive > 0 => Some(sum / count as f32),
_ => None
};
println!("Mean of the data is {:?}", mean);
}
第二个实例使用快速选择算法(quick select algorithm)计算中位数,该算法只对已知可能包含中位数的数据集的分区进行排序,从而避免了完整[排序][sort
]。该算法使用 [cmp
] 和 [Ordering
] 简便地地决定要检查的下一个分区,并使用 [split_at
] 为每个步骤的下一个分区选择一个任意的枢轴量。
use std::cmp::Ordering;
fn partition(data: &[i32]) -> Option<(Vec<i32>, i32, Vec<i32>)> {
match data.len() {
0 => None,
_ => {
let (pivot_slice, tail) = data.split_at(1);
let pivot = pivot_slice[0];
let (left, right) = tail.iter()
.fold((vec![], vec![]), |mut splits, next| {
{
let (ref mut left, ref mut right) = &mut splits;
if next < &pivot {
left.push(*next);
} else {
right.push(*next);
}
}
splits
});
Some((left, pivot, right))
}
}
}
fn select(data: &[i32], k: usize) -> Option<i32> {
let part = partition(data);
match part {
None => None,
Some((left, pivot, right)) => {
let pivot_idx = left.len();
match pivot_idx.cmp(&k) {
Ordering::Equal => Some(pivot),
Ordering::Greater => select(&left, k),
Ordering::Less => select(&right, k - (pivot_idx + 1)),
}
},
}
}
fn median(data: &[i32]) -> Option<f32> {
let size = data.len();
match size {
even if even % 2 == 0 => {
let fst_med = select(data, (even / 2) - 1);
let snd_med = select(data, even / 2);
match (fst_med, snd_med) {
(Some(fst), Some(snd)) => Some((fst + snd) as f32 / 2.0),
_ => None
}
},
odd => select(data, odd / 2).map(|x| x as f32)
}
}
fn main() {
let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
let part = partition(&data);
println!("Partition is {:?}", part);
let sel = select(&data, 5);
println!("Selection at ordered index {} is {:?}", 5, sel);
let med = median(&data);
println!("Median is {:?}", med);
}
最后一个实例使用可变的 [HashMap
] 来计算众数,[fold
] 和 [entry
] API 用来从集合中收集每个不同整数的计数。[HashMap
] 中最常见的值可以用 [max_by_key
] 取得。
use std::collections::HashMap;
fn main() {
let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
let frequencies = data.iter().fold(HashMap::new(), |mut freqs, value| {
*freqs.entry(value).or_insert(0) += 1;
freqs
});
let mode = frequencies
.into_iter()
.max_by_key(|&(_, count)| count)
.map(|(value, _)| *value);
println!("Mode of the data is {:?}", mode);
}
计算标准偏差
本实例计算一组测量值的标准偏差和 z 分数(z-score)。
标准偏差定义为方差的平方根(用 f32 浮点型的 [sqrt
] 计算),其中方差是每个测量值与平均数
之间的平方差的和
除以测量次数。
z 分数(z-score)是指单个测量值偏离数据集平均数
的标准差数。
fn mean(data: &[i32]) -> Option<f32> {
let sum = data.iter().sum::<i32>() as f32;
let count = data.len();
match count {
positive if positive > 0 => Some(sum / count as f32),
_ => None,
}
}
fn std_deviation(data: &[i32]) -> Option<f32> {
match (mean(data), data.len()) {
(Some(data_mean), count) if count > 0 => {
let variance = data.iter().map(|value| {
let diff = data_mean - (*value as f32);
diff * diff
}).sum::<f32>() / count as f32;
Some(variance.sqrt())
},
_ => None
}
}
fn main() {
let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
let data_mean = mean(&data);
println!("Mean is {:?}", data_mean);
let data_std_deviation = std_deviation(&data);
println!("Standard deviation is {:?}", data_std_deviation);
let zscore = match (data_mean, data_std_deviation) {
(Some(mean), Some(std_deviation)) => {
let diff = data[4] as f32 - mean;
Some(diff / std_deviation)
},
_ => None
};
println!("Z-score of data at index 4 (with value {}) is {:?}", data[4], zscore);
}
其它数学计算
大数
BigInt
使得超过 128 位的大整数计算成为可能。
use num::bigint::{BigInt, ToBigInt};
fn factorial(x: i32) -> BigInt {
if let Some(mut factorial) = 1.to_bigint() {
for i in 1..(x+1) {
factorial = factorial * i;
}
factorial
}
else {
panic!("Failed to calculate factorial!");
}
}
fn main() {
println!("{}! equals {}", 100, factorial(100));
}
文本处理
正则表达式
实例名称 | Crates | 类别 |
---|---|---|
验证并提取电子邮件登录信息 | ||
从文本提取标签元素唯一的列表 | ||
从文本提取电话号码 | ||
通过匹配多个正则表达式来筛选日志文件 | ||
文本模式替换 |
字符串解析
实例名称 | Crates | 类别 |
---|---|---|
收集 Unicode 字符 | ||
自定义结构体 并实现 FromStr trait |
正则表达式
验证并提取电子邮件登录信息
验证电子邮件地址的格式是否正确,并提取 @ 符号之前的所有内容。
use lazy_static::lazy_static;
use regex::Regex;
fn extract_login(input: &str) -> Option<&str> {
lazy_static! {
static ref RE: Regex = Regex::new(r"(?x)
^(?P<login>[^@\s]+)@
([[:word:]]+\.)*
[[:word:]]+$
").unwrap();
}
RE.captures(input).and_then(|cap| {
cap.name("login").map(|login| login.as_str())
})
}
fn main() {
assert_eq!(extract_login(r"I❤email@example.com"), Some(r"I❤email"));
assert_eq!(
extract_login(r"sdf+sdsfsd.as.sdsd@jhkk.d.rl"),
Some(r"sdf+sdsfsd.as.sdsd")
);
assert_eq!(extract_login(r"More@Than@One@at.com"), None);
assert_eq!(extract_login(r"Not an email@email"), None);
}
从文本提取标签元素唯一的列表
本实例展示从文本中提取、排序和去除标签列表的重复元素。
这里给出的标签正则表达式只捕获以字母开头的拉丁语标签,完整的 twitter 标签正则表达式要复杂得多。
use lazy_static::lazy_static;
use regex::Regex;
use std::collections::HashSet;
fn extract_hashtags(text: &str) -> HashSet<&str> {
lazy_static! {
static ref HASHTAG_REGEX : Regex = Regex::new(
r"\#[a-zA-Z][0-9a-zA-Z_]*"
).unwrap();
}
HASHTAG_REGEX.find_iter(text).map(|mat| mat.as_str()).collect()
}
fn main() {
let tweet = "Hey #world, I just got my new #dog, say hello to Till. #dog #forever #2 #_ ";
let tags = extract_hashtags(tweet);
assert!(tags.contains("#dog") && tags.contains("#forever") && tags.contains("#world"));
assert_eq!(tags.len(), 3);
}
从文本提取电话号码
使用 Regex::captures_iter
处理一个文本字符串,以捕获多个电话号码。这里的例子中是美国电话号码格式。
通过匹配多个正则表达式来筛选日志文件
读取名为 application.log
的文件,并且只输出包含下列内容的行:“version X.X.X”、端口为 443 的 IP 地址(如 “192.168.0.1:443”)、特定警告。
正则表达集构造器 regex::RegexSetBuilder
构建了正则表达式集 regex::RegexSet
。由于反斜杠在正则表达式中非常常见,因此使用原始字符串字面量可以使它们更具可读性。
文本模式替换
将所有出现的国际标准 ISO 8601 日期模式 YYYY-MM-DD 替换为具有斜杠的等效美式英语日期模式。例如: 2013-01-15
替换为 01/15/2013
。
Regex::replace_all
方法将替换整个正则表示匹配的所有内容。&str
实现了 Replacer
trait,它允许类似 $abcde
的变量引用相应的搜索匹配模式(search regex)中的命名捕获组 (?P<abcde>REGEX)
。有关示例和转义的详细信息,请参阅替换字符串语法。
译者注:正则表达式的使用,需要了解匹配规则:全文匹配(match regex)、搜索匹配(search regex)、替换匹配(replace regex)。
use lazy_static::lazy_static;
use std::borrow::Cow;
use regex::Regex;
fn reformat_dates(before: &str) -> Cow<str> {
lazy_static! {
static ref ISO8601_DATE_REGEX : Regex = Regex::new(
r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})"
).unwrap();
}
ISO8601_DATE_REGEX.replace_all(before, "$m/$d/$y")
}
fn main() {
let before = "2012-03-14, 2013-01-15 and 2014-07-05";
let after = reformat_dates(before);
assert_eq!(after, "03/14/2012, 01/15/2013 and 07/05/2014");
}
字符串解析
收集 Unicode 字符
使用 unicode-segmentation
crate 中的 UnicodeSegmentation::graphemes
函数,从 UTF-8 字符串中收集个别的 Unicode 字符。
use unicode_segmentation::UnicodeSegmentation;
fn main() {
let name = "José Guimarães\r\n";
let graphemes = UnicodeSegmentation::graphemes(name, true)
.collect::<Vec<&str>>();
assert_eq!(graphemes[3], "é");
}
自定义结构体
并实现 FromStr
trait
本实例中,创建一个自定义结构体 RGB
并实现 FromStr
trait,以将提供的颜色十六进制代码转换为其 RGB 颜色代码。
use std::str::FromStr;
#[derive(Debug, PartialEq)]
struct RGB {
r: u8,
g: u8,
b: u8,
}
impl FromStr for RGB {
type Err = std::num::ParseIntError;
// 解析格式为 '#rRgGbB..' 的颜色十六进制代码
// 将其转换为 'RGB' 实例
fn from_str(hex_code: &str) -> Result<Self, Self::Err> {
// u8::from_str_radix(src: &str, radix: u32)
// 将给定的字符串切片转换为 u8
let r: u8 = u8::from_str_radix(&hex_code[1..3], 16)?;
let g: u8 = u8::from_str_radix(&hex_code[3..5], 16)?;
let b: u8 = u8::from_str_radix(&hex_code[5..7], 16)?;
Ok(RGB { r, g, b })
}
}
fn main() {
let code: &str = &r"#fa7268";
match RGB::from_str(code) {
Ok(rgb) => {
println!(
r"The RGB color code is: R: {} G: {} B: {}",
rgb.r, rgb.g, rgb.b
);
}
Err(_) => {
println!("{} is not a valid color hex code!", code);
}
}
// 测试 from_str 函数执行是否符合预期
assert_eq!(
RGB::from_str(&r"#fa7268").unwrap(),
RGB {
r: 250,
g: 114,
b: 104
}
);
}
Web 编程
抓取网页
实例名称 | Crates | 类别 |
---|---|---|
从 HTML 网页中提取所有链接 | ||
检查网页死链 | ||
从 MediaWiki 标记页面提取所有唯一性链接 |
URL
实例名称 | Crates | 类别 |
---|---|---|
解析 URL 字符串为 Url 类型 | ||
通过移除路径段创建基本 URL | ||
从基本 URL 创建新 URLs | ||
提取 URL 源(scheme/ host/ port) | ||
从 URL 移除片段标识符和查询对 |
媒体类型(MIME)
实例名称 | Crates | 类别 |
---|---|---|
从字符串获取 MIME 类型 | ||
从文件名获取 MIME 类型 | ||
解析 HTTP 响应的 MIME 类型 |
客户端
请求处理
实例名称 | Crates | 类别 |
---|---|---|
发出 HTTP GET 请求 | ||
为 REST 请求设置自定义消息标头和 URL 参数 |
Web API 调用
实例名称 | Crates | 类别 |
---|---|---|
查询 GitHub API | ||
检查 API 资源是否存在 | ||
使用 GitHub API 创建和删除 Gist | ||
使用 RESTful API 分页 | ||
处理速率受限 API |
下载
实例名称 | Crates | 类别 |
---|---|---|
下载文件到临时目录 | ||
使用 HTTP range 请求头进行部分下载 | ||
POST 文件到 paste-rs |
提取链接
从 HTML 网页中提取所有链接
使用 reqwest::get
执行 HTTP GET 请求,然后使用 Document::from_read
将响应信息解析为 HTML 文档。以“a”(锚元素)作为结构体 Name
的参数,将结构体 Name
作为条件,使用 find
方法检索所有链接。在结构体 Selection
上调用 filter_map
方法,从具有 “href” attr
(属性)的链接检索所有 URL。
use error_chain::error_chain;
use select::document::Document;
use select::predicate::Name;
error_chain! {
foreign_links {
ReqError(reqwest::Error);
IoError(std::io::Error);
}
}
#[tokio::main]
async fn main() -> Result<()> {
let res = reqwest::get("https://www.rust-lang.org/en-US/")
.await?
.text()
.await?;
Document::from(res.as_str())
.find(Name("a"))
.filter_map(|n| n.attr("href"))
.for_each(|x| println!("{}", x));
Ok(())
}
检查网页死链
调用 get_base_url
方法检索 base URL
,如果 HTML 文档有 base
标签,从 base
标记获取 href attr
,初始 URL 的默认值是 Position::BeforePath
。
遍历 HTML 文档中的链接,并创建一个 tokio::spawn
任务,该任务将使用 url::ParseOptions
结构体和 Url::parse
方法解析单个链接。任务执行中,使用 reqwest 向链接发起请求,并验证状态码结构体 StatusCode
。实例中使用 await
异步等待任务完成,然后结束程序。
use error_chain::error_chain;
use reqwest::StatusCode;
use select::document::Document;
use select::predicate::Name;
use std::collections::HashSet;
use url::{Position, Url};
error_chain! {
foreign_links {
ReqError(reqwest::Error);
IoError(std::io::Error);
UrlParseError(url::ParseError);
JoinError(tokio::task::JoinError);
}
}
async fn get_base_url(url: &Url, doc: &Document) -> Result<Url> {
let base_tag_href = doc.find(Name("base")).filter_map(|n| n.attr("href")).nth(0);
let base_url =
base_tag_href.map_or_else(|| Url::parse(&url[..Position::BeforePath]), Url::parse)?;
Ok(base_url)
}
async fn check_link(url: &Url) -> Result<bool> {
let res = reqwest::get(url.as_ref()).await?;
Ok(res.status() != StatusCode::NOT_FOUND)
}
#[tokio::main]
async fn main() -> Result<()> {
let url = Url::parse("https://www.rust-lang.org/en-US/")?;
let res = reqwest::get(url.as_ref()).await?.text().await?;
let document = Document::from(res.as_str());
let base_url = get_base_url(&url, &document).await?;
let base_parser = Url::options().base_url(Some(&base_url));
let links: HashSet<Url> = document
.find(Name("a"))
.filter_map(|n| n.attr("href"))
.filter_map(|link| base_parser.parse(link).ok())
.collect();
let mut tasks = vec![];
for link in links {
tasks.push(tokio::spawn(async move {
if check_link(&link).await.unwrap() {
println!("{} is OK", link);
} else {
println!("{} is Broken", link);
}
}));
}
for task in tasks {
task.await?
}
Ok(())
}
从 MediaWiki 标记页面提取所有唯一性链接
使用 reqwest::get
获取 MediaWiki 页面的源代码,然后使用 Regex::captures_iter
查找内部和外部链接的所有条目。使用智能指针 Cow
可以提供对借用数据的不可变引用,避免分配过多的字符串
。
阅读 MediaWiki 链接语法详细了解。
use lazy_static::lazy_static;
use regex::Regex;
use std::borrow::Cow;
use std::collections::HashSet;
use std::error::Error;
fn extract_links(content: &str) -> HashSet<Cow<str>> {
lazy_static! {
static ref WIKI_REGEX: Regex = Regex::new(
r"(?x)
\[\[(?P<internal>[^\[\]|]*)[^\[\]]*\]\] # internal links
|
(url=|URL\||\[)(?P<external>http.*?)[ \|}] # external links
"
)
.unwrap();
}
let links: HashSet<_> = WIKI_REGEX
.captures_iter(content)
.map(|c| match (c.name("internal"), c.name("external")) {
(Some(val), None) => Cow::from(val.as_str().to_lowercase()),
(None, Some(val)) => Cow::from(val.as_str()),
_ => unreachable!(),
})
.collect();
links
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let content = reqwest::get(
"https://en.wikipedia.org/w/index.php?title=Rust_(programming_language)&action=raw",
)
.await?
.text()
.await?;
println!("{:#?}", extract_links(content.as_str()));
Ok(())
}
URL
解析 URL 字符串为 Url
类型
url
crate 中的 parse
方法验证并解析 &str
切片为 Url
结构体。如果输入字符串的格式不正确,解析方法 parse
会返回 Result<Url, ParseError>
。
一旦 URL 被解析,它就可以使用 Url
结构体类型中的所有方法。
use url::{Url, ParseError};
fn main() -> Result<(), ParseError> {
let s = "https://github.com/rust-lang/rust/issues?labels=E-easy&state=open";
let parsed = Url::parse(s)?;
println!("The path part of the URL is: {}", parsed.path());
Ok(())
}
通过移除路径段创建基本 URL
基本 URL(base URL)包括协议和域名。但基本 URL(base URL)不包括目录、文件或查询字符串,这些项都可以从给定的 URL 中剥离出来。创建基本 URL(base URL)时,通过 PathSegmentsMut::clear
方法移除目录和文件路径,通过方法 Url::set_query
移除查询字符串。
从基本 URL 创建新 URLs
join
方法从基路径和相对路径创建新的 URL。
use url::{Url, ParseError};
fn main() -> Result<(), ParseError> {
let path = "/rust-lang/cargo";
let gh = build_github_url(path)?;
assert_eq!(gh.as_str(), "https://github.com/rust-lang/cargo");
println!("The joined URL is: {}", gh);
Ok(())
}
fn build_github_url(path: &str) -> Result<Url, ParseError> {
const GITHUB: &'static str = "https://github.com";
let base = Url::parse(GITHUB).expect("hardcoded URL is known to be valid");
let joined = base.join(path)?;
Ok(joined)
}
提取 URL 源(scheme / host / port)
Url
结构体定义了多种方法,以便于提取有关它所表示的 URL 的信息。
use url::{Url, Host, ParseError};
fn main() -> Result<(), ParseError> {
let s = "ftp://rust-lang.org/examples";
let url = Url::parse(s)?;
assert_eq!(url.scheme(), "ftp");
assert_eq!(url.host(), Some(Host::Domain("rust-lang.org")));
assert_eq!(url.port_or_known_default(), Some(21));
println!("The origin is as expected!");
Ok(())
}
origin
方法产生相同的结果。
从 URL 移除片段标识符和查询对
解析 Url
结构体,并使用 url::Position
枚举对其进行切片,以去除不需要的 URL 片段。
use url::{Url, Position, ParseError};
fn main() -> Result<(), ParseError> {
let parsed = Url::parse("https://github.com/rust-lang/rust/issues?labels=E-easy&state=open")?;
let cleaned: &str = &parsed[..Position::AfterPath];
println!("cleaned: {}", cleaned);
Ok(())
}
媒介类型
从字符串获取 MIME 类型
下面的实例展示如何使用 mime crate 从字符串解析出 MIME
类型。FromStrError
结构体在 unwrap_or
子句中生成默认的 MIME
类型。
use mime::{Mime, APPLICATION_OCTET_STREAM};
fn main() {
let invalid_mime_type = "i n v a l i d";
let default_mime = invalid_mime_type
.parse::<Mime>()
.unwrap_or(APPLICATION_OCTET_STREAM);
println!(
"MIME for {:?} used default value {:?}",
invalid_mime_type, default_mime
);
let valid_mime_type = "TEXT/PLAIN";
let parsed_mime = valid_mime_type
.parse::<Mime>()
.unwrap_or(APPLICATION_OCTET_STREAM);
println!(
"MIME for {:?} was parsed as {:?}",
valid_mime_type, parsed_mime
);
}
从文件名获取 MIME 类型
下面的实例展示如何使用 mime crate 从给定的文件名返回正确的 MIME 类型。程序将检查文件扩展名并与已知的 MIME 类型列表匹配,返回值为 mime:Mime
。
use mime::Mime;
fn find_mimetype (filename : &String) -> Mime{
let parts : Vec<&str> = filename.split('.').collect();
let res = match parts.last() {
Some(v) =>
match *v {
"png" => mime::IMAGE_PNG,
"jpg" => mime::IMAGE_JPEG,
"json" => mime::APPLICATION_JSON,
&_ => mime::TEXT_PLAIN,
},
None => mime::TEXT_PLAIN,
};
return res;
}
fn main() {
let filenames = vec!("foobar.jpg", "foo.bar", "foobar.png");
for file in filenames {
let mime = find_mimetype(&file.to_owned());
println!("MIME for {}: {}", file, mime);
}
}
解析 HTTP 响应的 MIME 类型
当从 reqwest
接收到 HTTP 响应时,MIME 类型或媒体类型可以在实体头部的 Content-Type 标头中找到。reqwest::header::HeaderMap::get
方法将标头检索为结构体 reqwest::header::HeaderValue
,结构体可以转换为字符串。然后 mime
crate 可以解析它,生成 mime::Mime
值。
mime
crate 也定义了一些常用的 MIME 类型。
请注意:reqwest::header
模块是从 http
crate 导出的。
use error_chain::error_chain;
use mime::Mime;
use std::str::FromStr;
use reqwest::header::CONTENT_TYPE;
error_chain! {
foreign_links {
Reqwest(reqwest::Error);
Header(reqwest::header::ToStrError);
Mime(mime::FromStrError);
}
}
#[tokio::main]
async fn main() -> Result<()> {
let response = reqwest::get("https://www.rust-lang.org/logos/rust-logo-32x32.png").await?;
let headers = response.headers();
match headers.get(CONTENT_TYPE) {
None => {
println!("The response does not contain a Content-Type header.");
}
Some(content_type) => {
let content_type = Mime::from_str(content_type.to_str()?)?;
let media_type = match (content_type.type_(), content_type.subtype()) {
(mime::TEXT, mime::HTML) => "a HTML document",
(mime::TEXT, _) => "a text document",
(mime::IMAGE, mime::PNG) => "a PNG image",
(mime::IMAGE, _) => "an image",
_ => "neither text nor image",
};
println!("The reponse contains {}.", media_type);
}
};
Ok(())
}
客户端
请求处理
实例名称 | Crates | 类别 |
---|---|---|
发出 HTTP GET 请求 | ||
为 REST 请求设置自定义消息标头和 URL 参数 |
Web API 调用
实例名称 | Crates | 类别 |
---|---|---|
查询 GitHub API | ||
检查 API 资源是否存在 | ||
使用 GitHub API 创建和删除 Gist | ||
使用 RESTful API 分页 | ||
处理速率受限 API |
下载
实例名称 | Crates | 类别 |
---|---|---|
下载文件到临时目录 | ||
使用 HTTP range 请求头进行部分下载 | ||
POST 文件到 paste-rs |
请求处理
发出 HTTP GET 请求
解析提供的 URL,并使用 reqwest::blocking::get
发起同步 HTTP GET 请求。打印获取的响应消息状态和标头 reqwest::blocking::Response
。使用 read_to_string
将 HTTP 响应消息主体正文读入到指派的字符串 String
。
use error_chain::error_chain;
use std::io::Read;
error_chain! {
foreign_links {
Io(std::io::Error);
HttpRequest(reqwest::Error);
}
}
fn main() -> Result<()> {
let mut res = reqwest::blocking::get("http://httpbin.org/get")?;
let mut body = String::new();
res.read_to_string(&mut body)?;
println!("Status: {}", res.status());
println!("Headers:\n{:#?}", res.headers());
println!("Body:\n{}", body);
Ok(())
}
异步
常见的方法是通过包含 tokio
在内的类似异步执行器,使主函数执行异步,但检索处理相同的信息。
本实例中,tokio::main
处理所有繁重的执行器设置,并允许在 .await
之前不阻塞的按顺序执行代码。
也可以使用 reqwest 的异步版本,其请求函数 reqwest::get
和响应结构体 reqwest::Response
都是异步的。
use error_chain::error_chain;
error_chain! {
foreign_links {
Io(std::io::Error);
HttpRequest(reqwest::Error);
}
}
#[tokio::main]
async fn main() -> Result<()> {
let res = reqwest::get("http://httpbin.org/get").await?;
println!("Status: {}", res.status());
println!("Headers:\n{:#?}", res.headers());
let body = res.text().await?;
println!("Body:\n{}", body);
Ok(())
}
为 REST 请求设置自定义消息标头和 URL 参数
本实例中为 HTTP GET 请求设置标准的和自定义的 HTTP 消息标头以及 URL 参数。使用 hyper::header!
宏创建 XPoweredBy
类型的自定义消息标头。
使用 Url::parse_with_params
构建复杂的 URL。使用 RequestBuilder::header
方法设置标准消息标头 header::UserAgent
、header::Authorization
,以及自定义类型 XPoweredBy
,然后使用 RequestBuilder::send
发起请求。
请求的服务目标为 http://httpbin.org/headers,其响应结果是包含所有请求的消息标头的 JSON 字典,易于验证。
Web API 调用
查询 GitHub API
使用 reqwest::get
查询 点赞的用户 API v3,以获取某个 GitHub 项目的所有点赞用户的列表。使用 Response::json
将响应信息 reqwest::Response
反序列化为实现了 serde::Deserialize
trait 的 User
对象。
tokio::main 用于设置异步执行器,该进程异步等待 reqwest::get
完成,然后将响应信息反序列化到用户实例中。
use serde::Deserialize;
use reqwest::Error;
#[derive(Deserialize, Debug)]
struct User {
login: String,
id: u32,
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let request_url = format!("https://api.github.com/repos/{owner}/{repo}/stargazers",
owner = "rust-lang-nursery",
repo = "rust-cookbook");
println!("{}", request_url);
let response = reqwest::get(&request_url).await?;
let users: Vec<User> = response.json().await?;
println!("{:?}", users);
Ok(())
}
检查 API 资源是否存在
使用消息标头 HEAD 请求((Client::head
)查询 GitHub 用户端接口,然后检查响应代码以确定是否成功。这是一种无需接收 HTTP 响应消息主体,即可快速查询 rest 资源的方法。使用 ClientBuilder::timeout
方法配置的 reqwest::Client
结构体将确保请求不会超时。
由于 ClientBuilder::build
和 RequestBuilder::send
都返回错误类型 reqwest::Error
,所以便捷的 reqwest::Result
类型被用于主函数的返回类型。
use reqwest::Result;
use std::time::Duration;
use reqwest::ClientBuilder;
#[tokio::main]
async fn main() -> Result<()> {
let user = "ferris-the-crab";
let request_url = format!("https://api.github.com/users/{}", user);
println!("{}", request_url);
let timeout = Duration::new(5, 0);
let client = ClientBuilder::new().timeout(timeout).build()?;
let response = client.head(&request_url).send().await?;
if response.status().is_success() {
println!("{} is a user!", user);
} else {
println!("{} is not a user!", user);
}
Ok(())
}
使用 GitHub API 创建和删除 Gist
使用 Client::post
创建一个 POST 请求提交到 GitHub gists API v3 接口的 gist,并使用 Client::delete
使用 DELETE 请求删除它。
reqwest::Client
负责这两个请求的详细信息,包括:URL、消息体(body)和身份验证。serde_json::json!
宏的 POST 主体可以提供任意形式的 JSON 主体,通过调用 RequestBuilder::json
设置请求主体,RequestBuilder::basic_auth
处理身份验证。本实例中调用 RequestBuilder::send
方法同步执行请求。
use error_chain::error_chain;
use serde::Deserialize;
use serde_json::json;
use std::env;
use reqwest::Client;
error_chain! {
foreign_links {
EnvVar(env::VarError);
HttpRequest(reqwest::Error);
}
}
#[derive(Deserialize, Debug)]
struct Gist {
id: String,
html_url: String,
}
#[tokio::main]
async fn main() -> Result<()> {
let gh_user = env::var("GH_USER")?;
let gh_pass = env::var("GH_PASS")?;
let gist_body = json!({
"description": "the description for this gist",
"public": true,
"files": {
"main.rs": {
"content": r#"fn main() { println!("hello world!");}"#
}
}});
let request_url = "https://api.github.com/gists";
let response = Client::new()
.post(request_url)
.basic_auth(gh_user.clone(), Some(gh_pass.clone()))
.json(&gist_body)
.send().await?;
let gist: Gist = response.json().await?;
println!("Created {:?}", gist);
let request_url = format!("{}/{}",request_url, gist.id);
let response = Client::new()
.delete(&request_url)
.basic_auth(gh_user, Some(gh_pass))
.send().await?;
println!("Gist {} deleted! Status code: {}",gist.id, response.status());
Ok(())
}
实例中使用 HTTP 基本认证 为了授权访问 GitHub API。实际应用中或许将使用一个更为复杂的 OAuth 授权流程。
使用 RESTful API 分页
可以将分页的 web API 方便地包裹在 Rust 迭代器中,当到达每一页的末尾时,迭代器会从远程服务器加载下一页结果。
use reqwest::Result;
use serde::Deserialize;
#[derive(Deserialize)]
struct ApiResponse {
dependencies: Vec<Dependency>,
meta: Meta,
}
#[derive(Deserialize)]
struct Dependency {
crate_id: String,
}
#[derive(Deserialize)]
struct Meta {
total: u32,
}
struct ReverseDependencies {
crate_id: String,
dependencies: <Vec<Dependency> as IntoIterator>::IntoIter,
client: reqwest::blocking::Client,
page: u32,
per_page: u32,
total: u32,
}
impl ReverseDependencies {
fn of(crate_id: &str) -> Result<Self> {
Ok(ReverseDependencies {
crate_id: crate_id.to_owned(),
dependencies: vec![].into_iter(),
client: reqwest::blocking::Client::new(),
page: 0,
per_page: 100,
total: 0,
})
}
fn try_next(&mut self) -> Result<Option<Dependency>> {
if let Some(dep) = self.dependencies.next() {
return Ok(Some(dep));
}
if self.page > 0 && self.page * self.per_page >= self.total {
return Ok(None);
}
self.page += 1;
let url = format!("https://crates.io/api/v1/crates/{}/reverse_dependencies?page={}&per_page={}",
self.crate_id,
self.page,
self.per_page);
let response = self.client.get(&url).send()?.json::<ApiResponse>()?;
self.dependencies = response.dependencies.into_iter();
self.total = response.meta.total;
Ok(self.dependencies.next())
}
}
impl Iterator for ReverseDependencies {
type Item = Result<Dependency>;
fn next(&mut self) -> Option<Self::Item> {
match self.try_next() {
Ok(Some(dep)) => Some(Ok(dep)),
Ok(None) => None,
Err(err) => Some(Err(err)),
}
}
}
fn main() -> Result<()> {
for dep in ReverseDependencies::of("serde")? {
println!("reverse dependency: {}", dep?.crate_id);
}
Ok(())
}
处理速率受限 API
此实例使用 GitHub API - 速率限制展示如何处理远程服务器错误。本实例使用 hyper::header!
宏来解析响应头并检查 reqwest::StatusCode::Forbidden
。如果响应超过速率限制,则将等待并重试。
下载
下载文件到临时目录
使用 tempfile::Builder
创建一个临时目录,并使用 reqwest::get
通过 HTTP 协议异步下载文件。
使用 Response::url
方法内部的 tempdir()
方法获取文件名字,使用 File
结构体创建目标文件,并使用 io::copy
将下载的数据复制到文件中。程序退出时,会自动删除临时目录。
use error_chain::error_chain;
use std::io::copy;
use std::fs::File;
use tempfile::Builder;
error_chain! {
foreign_links {
Io(std::io::Error);
HttpRequest(reqwest::Error);
}
}
#[tokio::main]
async fn main() -> Result<()> {
let tmp_dir = Builder::new().prefix("example").tempdir()?;
let target = "https://www.rust-lang.org/logos/rust-logo-512x512.png";
let response = reqwest::get(target).await?;
let mut dest = {
let fname = response
.url()
.path_segments()
.and_then(|segments| segments.last())
.and_then(|name| if name.is_empty() { None } else { Some(name) })
.unwrap_or("tmp.bin");
println!("file to download: '{}'", fname);
let fname = tmp_dir.path().join(fname);
println!("will be located under: '{:?}'", fname);
File::create(fname)?
};
let content = response.text().await?;
copy(&mut content.as_bytes(), &mut dest)?;
Ok(())
}
使用 HTTP range 请求头进行部分下载
使用 reqwest::blocking::Client::head
获取响应的消息主体的大小(即消息主体内容长度)。
然后,使用 reqwest::blocking::Client::get
下载 10240 字节的内容,同时打印进度消息。本实例使用同步的 reqwest 模块,消息范围标头指定响应的消息块大小和位置。
译者注:RFC(Request For Comments)是一系列以编号排定的文件。文件收集了有关互联网相关信息,以及 UNIX 和互联网社区的软件文件。
use error_chain::error_chain;
use reqwest::header::{HeaderValue, CONTENT_LENGTH, RANGE};
use reqwest::StatusCode;
use std::fs::File;
use std::str::FromStr;
error_chain! {
foreign_links {
Io(std::io::Error);
Reqwest(reqwest::Error);
Header(reqwest::header::ToStrError);
}
}
struct PartialRangeIter {
start: u64,
end: u64,
buffer_size: u32,
}
impl PartialRangeIter {
pub fn new(start: u64, end: u64, buffer_size: u32) -> Result<Self> {
if buffer_size == 0 {
Err("invalid buffer_size, give a value greater than zero.")?;
}
Ok(PartialRangeIter {
start,
end,
buffer_size,
})
}
}
impl Iterator for PartialRangeIter {
type Item = HeaderValue;
fn next(&mut self) -> Option<Self::Item> {
if self.start > self.end {
None
} else {
let prev_start = self.start;
self.start += std::cmp::min(self.buffer_size as u64, self.end - self.start + 1);
Some(HeaderValue::from_str(&format!("bytes={}-{}", prev_start, self.start - 1)).expect("string provided by format!"))
}
}
}
fn main() -> Result<()> {
let url = "https://httpbin.org/range/102400?duration=2";
const CHUNK_SIZE: u32 = 10240;
let client = reqwest::blocking::Client::new();
let response = client.head(url).send()?;
let length = response
.headers()
.get(CONTENT_LENGTH)
.ok_or("response doesn't include the content length")?;
let length = u64::from_str(length.to_str()?).map_err(|_| "invalid Content-Length header")?;
let mut output_file = File::create("download.bin")?;
println!("starting download...");
for range in PartialRangeIter::new(0, length - 1, CHUNK_SIZE)? {
println!("range {:?}", range);
let mut response = client.get(url).header(RANGE, range).send()?;
let status = response.status();
if !(status == StatusCode::OK || status == StatusCode::PARTIAL_CONTENT) {
error_chain::bail!("Unexpected server response: {}", status)
}
std::io::copy(&mut response, &mut output_file)?;
}
let content = response.text()?;
std::io::copy(&mut content.as_bytes(), &mut output_file)?;
println!("Finished with success!");
Ok(())
}
POST 文件到 paste-rs
本实例使用 reqwest::Client
建立与 https://paste.rs 的连接,遵循 reqwest::RequestBuilder
结构体模式。调用 Client::post
方法,以 URL 为参数连接目标,RequestBuilder::body
通过读取文件设置要发送的内容,RequestBuilder::send
方法在文件上传过程中将一直阻塞,直到返回响应消息。最后,read_to_string
返回响应消息并显示在控制台中。
use error_chain::error_chain;
use std::fs::File;
use std::io::Read;
error_chain! {
foreign_links {
HttpRequest(reqwest::Error);
IoError(::std::io::Error);
}
}
#[tokio::main]
async fn main() -> Result<()> {
let paste_api = "https://paste.rs";
let mut file = File::open("message")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let client = reqwest::Client::new();
let res = client.post(paste_api)
.body(contents)
.send()
.await?;
let response_text = res.text().await?;
println!("Your paste is located at: {}",response_text );
Ok(())
}