Rust Cookbook 中文版

中文译注(Chinese translation of the Rust Cookbook):

  1. 《Rust Cookbook 中文版》 翻译自 The Rust Cookbook(Cookin' with Rust),查看此书的 Github 翻译项目
  2. 本书翻译内容源自 zzy的开源的翻译版本rust-lang-cn 开源组织对原译者感激不尽!
  3. 许可协议:跟随原翻译版本的 MIT 和 Apache 2.0 双许可授权。另本站的英文版保持原有的 CC0 协议。
  4. 本站支持文档中英文切换,点击页面右上角语言图标可切换到相同章节的英文页面,英文版每天都会自动同步一次官方的最新版本
  5. 若发现本页表达错误或帮助我们改进翻译,可点击右上角的编辑按钮打开本页对应源码文件进行编辑和修改,Rust 中文资源的开源组织发展离不开大家,感谢您的支持和帮助!

《Rust Cookbook 中文版》是 Rust 程序设计语言(Rust 官方教程简体中文版)的简要实例示例集合:展示了在 Rust 生态系统中,使用各类 crate 来完成常见编程任务的良好实践。

了解更多关于《Rust Cookbook 中文版》一书的信息,请阅读关于本书,包括:如何阅读本书的提示、如何使用实例示例,以及关于注释的约定。

参与贡献

Rust Cookbook 项目(英文和中文两者)的目的是让 Rust 程序员新手能够更容易地参与到 Rust 社区中,因此欢迎你做出贡献,详情可查看“参与贡献”文件。

算法

生成随机值

实例名称Crates类别
生成随机数rand-badgecat-science-badge
生成范围内随机数rand-badgecat-science-badge
生成给定分布随机数rand-badge rand_distr-badgecat-science-badge
生成自定义类型随机值rand-badgecat-science-badge
从一组字母数字字符创建随机密码rand-badgecat-os-badge
从一组用户定义字符创建随机密码rand-badgecat-os-badge

Vector 排序

实例名称Crates类别
整数 Vector 排序std-badgecat-science-badge
浮点数 Vector 排序std-badgecat-science-badge
结构体 Vector 排序std-badgecat-science-badge

命令行

参数解析

实例名称Crates类别
解析命令行参数clap-badgecat-command-line-badge

ANSI 终端

实例名称Crates类别
ANSI 终端ansi_term-badgecat-command-line-badge

压缩

使用 tar 包

实例名称Crates类别
解压 tar 包flate2-badge tar-badgecat-compression-badge
压缩目录为 tar 包flate2-badge tar-badgecat-compression-badge
从路径移除前缀时,解压 tar 包flate2-badge tar-badgecat-compression-badge

并发/并行

显式线程

实例名称Crates类别
生成短期线程crossbeam-badgecat-concurrency-badge
创建并发的数据管道crossbeam-badgecat-concurrency-badge
在两个线程间传递数据crossbeam-badgecat-concurrency-badge
保持全局可变状态lazy_static-badgecat-rust-patterns-badge
对所有 iso 文件的 SHA256 值并发求和threadpool-badge walkdir-badge num_cpus-badge ring-badgecat-concurrency-badgecat-filesystem-badge
将绘制分形的线程分派到线程池threadpool-badge num-badge num_cpus-badge image-badgecat-concurrency-badgecat-science-badgecat-rendering-badge

数据并行

实例名称Crates类别
并行改变数组中元素rayon-badgecat-concurrency-badge
并行测试集合中任意或所有的元素是否匹配给定断言rayon-badgecat-concurrency-badge
使用给定断言并行搜索项rayon-badgecat-concurrency-badge
对 vector 并行排序rayon-badge rand-badgecat-concurrency-badge
Map-reduce 并行计算rayon-badgecat-concurrency-badge
并行生成 jpg 缩略图rayon-badge glob-badge image-badgecat-concurrency-badgecat-filesystem-badge

密码学

散列(哈希)

实例名称Crates类别
计算文件的 SHA-256 摘要ring-badge data-encoding-badgecat-cryptography-badge
使用 HMAC 摘要对消息进行签名和验证ring-badgecat-cryptography-badge

加密

实例名称Crates类别
使用 PBKDF2 对密码进行加密(salt)和散列(hash)运算ring-badge data-encoding-badgecat-cryptography-badge

数据结构

位域

实例名称Crates类别
定义并操作位域风格的类型bitflags-badgecat-no-std-badge

数据库

SQLite

实例名称Crates类别
创建 SQLite 数据库rusqlite-badgecat-database-badge
数据插入和查询rusqlite-badgecat-database-badge
事务处理rusqlite-badgecat-database-badge

使用 Postgres

实例名称Crates类别
Postgres 数据库中创建表postgres-badgecat-database-badge
数据插入和查询postgres-badgecat-database-badge
数据聚合postgres-badgecat-database-badge

日期及时间

期间和计算

实例名称Crates类别
测量运行时间std-badgecat-time-badge
执行日期检查和时间计算chrono-badgecat-date-and-time-badge
时间的时区转换chrono-badgecat-date-and-time-badge

解析与显示

实例名称Crates类别
检查日期和时间chrono-badgecat-date-and-time-badge
日期和 UNIX 时间戳的互相转换chrono-badgecat-date-and-time-badge
日期和时间的格式化显示chrono-badgecat-date-and-time-badge
将字符串解析为 DateTime 结构体chrono-badgecat-date-and-time-badge

开发工具

调试工具

日志信息

实例名称Crates类别
记录调试信息到控制台log-badge env_logger-badgecat-debugging-badge
记录错误信息到控制台log-badge env_logger-badgecat-debugging-badge
记录信息时,用标准输出 stdout 替换标准错误 stderrlog-badge env_logger-badgecat-debugging-badge
使用自定义日志记录器记录信息log-badgecat-debugging-badge
记录到 Unix 系统日志log-badge syslog-badgecat-debugging-badge

日志配置

实例名称Crates类别
启用每个模块的日志级别log-badge env_logger-badgecat-debugging-badge
用自定义环境变量设置日志记录log-badge env_logger-badgecat-debugging-badge
在日志信息中包含时间戳log-badge env_logger-badge chrono-badgecat-debugging-badge
将信息记录到自定义位置log-badge log4rs-badgecat-debugging-badge

版本控制

实例名称Crates类别
解析并递增版本字符串semver-badgecat-config-badge
解析复杂的版本字符串semver-badgecat-config-badge
检查给定版本是否为预发布版本semver-badgecat-config-badge
查询适配给定范围的最新版本semver-badgecat-config-badge
检查外部命令的版本兼容性semver-badgecat-text-processing-badge cat-os-badge

构建时

实例名称Crates类别
编译并静态链接到绑定的 C 语言库cc-badgecat-development-tools-badge
编译并静态链接到绑定的 C++ 语言库cc-badgecat-development-tools-badge
编译 C 语言库时自定义设置cc-badgecat-development-tools-badge

编码

字符集

实例名称Crates类别
百分比编码(URL 编码)字符串percent-encoding-badgecat-encoding-badge
将字符串编码为 application/x-www-form-urlencodedurl-badgecat-encoding-badge
编码和解码十六进制data-encoding-badgecat-encoding-badge
编码和解码 base64base64-badgecat-encoding-badge

CSV 处理

实例名称Crates类别
读取 CSV 记录csv-badgecat-encoding-badge
读取有不同分隔符的 CSV 记录csv-badgecat-encoding-badge
筛选匹配断言的 CSV 记录csv-badgecat-encoding-badge
用 Serde 处理无效的 CSV 数据csv-badge serde-badgecat-encoding-badge
将记录序列化为 CSVcsv-badgecat-encoding-badge
用 Serde 将记录序列化为 CSVcsv-badge serde-badgecat-encoding-badge
转换 CSV 文件的列csv-badge serde-badgecat-encoding-badge

结构化数据

实例名称Crates类别
对非结构化 JSON 序列化和反序列化serde-json-badgecat-encoding-badge
反序列化 TOML 配置文件toml-badgecat-encoding-badge
以小端模式(低位模式)字节顺序读写整数byteorder-badgecat-encoding-badge

错误处理

处理错误变量

实例名称Crates类别
在 main 方法中对错误适当处理error-chain-badgecat-rust-patterns-badge
避免在错误转变过程中遗漏错误error-chain-badgecat-rust-patterns-badge
获取复杂错误场景的回溯error-chain-badgecat-rust-patterns-badge

文件系统

文件读写

实例名称Crates类别
读取文件的字符串行std-badgecat-filesystem-badge
避免读取写入同一文件same_file-badgecat-filesystem-badge
使用内存映射随机访问文件memmap-badgecat-filesystem-badge

目录遍历

实例名称Crates类别
过去 24 小时内修改过的文件名std-badgecat-filesystem-badge cat-os-badge
查找给定路径的循环same_file-badgecat-filesystem-badge
递归查找重名文件walkdir-badgecat-filesystem-badge
使用给定断言递归查找所有文件walkdir-badgecat-filesystem-badge
跳过隐藏文件遍历目录walkdir-badgecat-filesystem-badge
在给定深度的目录,递归计算文件大小walkdir-badgecat-filesystem-badge
递归查找所有 png 文件glob-badgecat-filesystem-badge
忽略文件名大小写,使用给定模式查找所有文件glob-badgecat-filesystem-badge

硬件支持

处理器

实例名称Crates类别
检查逻辑 cpu 内核的数量num_cpus-badgecat-hardware-support-badge

内存管理

常量

实例名称Crates类别
声明延迟计算常量lazy_static-badgecat-caching-badge cat-rust-patterns-badge

网络

服务器

实例名称Crates类别
监听未使用的 TCP/IP 端口std-badgecat-net-badge

操作系统

外部命令

实例名称Crates类别
运行外部命令并处理 stdoutregex-badgecat-os-badge cat-text-processing-badge
运行传递 stdin 的外部命令,并检查错误代码regex-badgecat-os-badge cat-text-processing-badge
运行管道传输的外部命令std-badgecat-os-badge
将子进程的 stdout 和 stderr 重定向到同一个文件std-badgecat-os-badge
持续处理子进程的输出std-badgecat-os-badgecat-text-processing-badge
读取环境变量std-badgecat-os-badge

科学计算

数学

线性代数

实例名称Crates类别
Vector 范数ndarray-badgecat-science-badge
Vector 比较ndarray-badgecat-science-badge
矩阵相加ndarray-badgecat-science-badge
矩阵相乘ndarray-badgecat-science-badge
标量、vector、矩阵相乘ndarray-badgecat-science-badge
矩阵求逆nalgebra-badgecat-science-badge
(反)序列化矩阵ndarray-badgecat-science-badge

三角学

实例名称Crates类别
计算三角形的边长std-badgecat-science-badge
验证正切(tan)等于正弦(sin)除以余弦(cos)std-badgecat-science-badge
地球上两点之间的距离std-badgecat-science-badge

复数

实例名称Crates类别
创建复数num-badgecat-science-badge
复数相加num-badgecat-science-badge
复数的数学函数num-badgecat-science-badge

统计学

实例名称Crates类别
集中趋势度量std-badgecat-science-badge
计算标准偏差std-badgecat-science-badge

其它数学计算

实例名称Crates类别
大数num-badgecat-science-badge

文本处理

正则表达式

实例名称Crates类别
验证并提取电子邮件登录信息regex-badge lazy_static-badgecat-text-processing-badge
从文本提取标签元素唯一的列表regex-badge lazy_static-badgecat-text-processing-badge
从文本提取电话号码regex-badgecat-text-processing-badge
通过匹配多个正则表达式来筛选日志文件regex-badgecat-text-processing-badge
文本模式替换regex-badge lazy_static-badgecat-text-processing-badge

字符串解析

实例名称Crates类别
收集 Unicode 字符unicode-segmentation-badgecat-encoding-badge
自定义结构体并实现 FromStr traitstd-badgecat-text-processing-badge

Web 编程

抓取网页

实例名称Crates类别
从 HTML 网页中提取所有链接reqwest-badge select-badgecat-net-badge
检查网页死链reqwest-badge select-badge url-badgecat-net-badge
从 MediaWiki 标记页面提取所有唯一性链接reqwest-badge regex-badgecat-net-badge

URL

实例名称Crates类别
解析 URL 字符串为 Url 类型url-badgecat-net-badge
通过移除路径段创建基本 URLurl-badgecat-net-badge
从基本 URL 创建新 URLsurl-badgecat-net-badge
提取 URL 源(scheme/ host/ port)url-badgecat-net-badge
从 URL 移除片段标识符和查询对url-badgecat-net-badge

媒体类型(MIME)

实例名称Crates类别
从字符串获取 MIME 类型mime-badgecat-encoding-badge
从文件名获取 MIME 类型mime-badgecat-encoding-badge
解析 HTTP 响应的 MIME 类型mime-badge reqwest-badgecat-net-badge cat-encoding-badge

客户端

请求处理

实例名称Crates类别
发出 HTTP GET 请求reqwest-badgecat-net-badge
为 REST 请求设置自定义消息标头和 URL 参数reqwest-badge hyper-badge url-badge cat-net-badge

Web API 调用

实例名称Crates类别
查询 GitHub APIreqwest-badge serde-badgecat-net-badge cat-encoding-badge
检查 API 资源是否存在reqwest-badgecat-net-badge
使用 GitHub API 创建和删除 Gistreqwest-badge serde-badgecat-net-badge cat-encoding-badge
使用 RESTful API 分页reqwest-badge serde-badgecat-net-badge cat-encoding-badge
处理速率受限 APIreqwest-badge hyper-badge cat-net-badge

下载

实例名称Crates类别
下载文件到临时目录reqwest-badge tempdir-badgecat-net-badge cat-filesystem-badge
使用 HTTP range 请求头进行部分下载reqwest-badgecat-net-badge
POST 文件到 paste-rsreqwest-badgecat-net-badge

关于本书

目录

指南目标读者

本指南适用于 Rust 程序员新手,以便于他们可以快速了解 Rust crate 生态系统的功能。同时,本指南也适用于经验丰富的 Rust 程序员,他们可以在本指南中找到如何完成常见任务的简单提示。

如何阅读指南

指南的索引包含完整的实例列表,组织为数个章节:基础、编码、并发等。这些章节基本是按照顺序排列的;后面的章节更深入一些,并且有些实例是在前面章节的概念之上构建。

索引中,每个章节都包含实例列表。实例名称是要完成任务的简单描述,比如“在一个范围内生成随机数”;每个实例都有标记指示所使用的 crates,比如 rand-badge;以及 crate 在 crates.io 上的分类,比如 cat-science-badge

Rust 程序员新手应该按照由第一章节直至最后章节的顺序来阅读,这种方式易于理解书中的实例。同时,这样也可以对 crate 生态系统有一个全面的了解。点击索引中的章节标题,或者在侧边栏中导航到指南的章节页面。

如果你只是在简单地为一个任务的寻找解决方案,那么指南就较难以导航。找到特定实例的最简单的方法是详细查看索引,寻找感兴趣的 crate 及其类别,然后点击实例的名称来阅读它。指南的导航和浏览还在改进,以后或许会有所改善。

如何使用实例

指南的设计是为了让你能够即时访问可工作的代码,以及对其正在做什么有一个完整阐述,并指导你了解如何更进一步的信息。

指南中的所有实例都是完整的、可独立运行的程序,因此你可以直接复制它们到自己的项目中进行试验。为此,请按照以下说明进行操作。

考虑这个实例:“在一个范围内,生成随机数”:

rand-badge cat-science-badge

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! 宏自定义 ErrorResult 类型,以及来自两种标准库的错误类型的自动转换。自动转换使得 ? 操作符正常运作。

在默认情况下——基于可读性目的——错误处理模板是隐藏的,如下代码所示。要阅读完整代码,请单击位于代码段右上角的“展开”()按钮。

use error_chain::error_chain; use url::{Url, Position}; error_chain! { foreign_links { UrlParse(url::ParseError); } } fn main() -> Result<()> { let parsed = Url::parse("https://httpbin.org/cookies/set?k2=v2&k1=v1")?; let cleaned: &str = &parsed[..Position::AfterPath]; println!("cleaned: {}", cleaned); Ok(()) }

要了解更多关于 Rust 错误处理的背景知识,请阅读 Rust 程序设计语言(2018)中的错误处理章节,以及这篇博客文章

关于 crate 的说明

本指南的最终目的是对 Rust crate 生态系统的广泛覆盖,但目前还处于引导和演示过程中,因此覆盖范围有限。我们希望从小范围开始,然后慢慢拓展,这将有助于指南更快地成为高质量的资源,并使其在增长过程中保持一致的质量水平。

目前,本指南聚焦在标准库、以及“核心”或“基础”方面的 crate ——这些 crate 构成了最常见的编程任务,而生态系统的其它部分则是以此为基础构建的。

本指南与 Rust Libz Blitz 密切相关,后者是一个用于识别并提高 crate 质量的项目,因此它在很大程度上推迟了 crate 的选择。任何已经走完评估过程的 crate,都在本指南的撰写范围内,而且等待评估的 crate 也是如此。

算法

生成随机值

实例名称Crates类别
生成随机数rand-badgecat-science-badge
生成范围内随机数rand-badgecat-science-badge
生成给定分布随机数rand-badge rand_distr-badgecat-science-badge
生成自定义类型随机值rand-badgecat-science-badge
从一组字母数字字符创建随机密码rand-badgecat-os-badge
从一组用户定义字符创建随机密码rand-badgecat-os-badge

Vector 排序

实例名称Crates类别
整数 Vector 排序std-badgecat-science-badge
浮点数 Vector 排序std-badgecat-science-badge
结构体 Vector 排序std-badgecat-science-badge

生成随机值

生成随机数

rand-badge cat-science-badge

在随机数生成器 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>()); }

生成范围内随机数

rand-badge cat-science-badge

使用 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_distr-badge cat-science-badge

默认情况下,随机数在 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(()) }

生成自定义类型随机值

rand-badge cat-science-badge

随机生成一个元组 (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); }

从一组字母数字字符创建随机密码

rand-badge cat-os-badge

随机生成一个给定长度的 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); }

从一组用户定义字符创建随机密码

rand-badge cat-os-badge

使用用户自定义的字节字符串,使用 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 排序

std-badge cat-science-badge

这个实例通过 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 排序

std-badge cat-science-badge

f32 或 f64 的 vector,可以使用 vec::sort_byPartialOrd::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 排序

std-badge cat-science-badge

依据自然顺序(按名称和年龄),对具有 nameage 属性的 Person 结构体 Vector 排序。为了使 Person 可排序,你需要四个 traits:EqPartialEqOrd,以及 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类别
解析命令行参数clap-badgecat-command-line-badge

ANSI 终端

实例名称Crates类别
ANSI 终端ansi_term-badgecat-command-line-badge

参数解析

解析命令行参数

clap-badge cat-command-line-badge

此应用程序使用 clap 构建器样式描述其命令行界面的结构。文档还提供了另外两种可用的方法去实例化应用程序。

在构建器样式中,with_name 函数是 value_of 方法将用于检索传递值的唯一标识符。shortlong 选项控制用户将要键入的标志;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-badge cat-command-line-badge

此程序描述了 ansi_term crate 的使用方法,以及它如何用于控制 ANSI 终端上的颜色和格式,如蓝色粗体文本或黄色下划线文本。

ansi_term 中有两种主要的数据结构:ANSIStringStyleStyle 包含样式信息:颜色,是否粗体文本,或者是否闪烁,或者其它样式。还有 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 包flate2-badge tar-badgecat-compression-badge
压缩目录为 tar 包flate2-badge tar-badgecat-compression-badge
从路径移除前缀时,解压 tar 包flate2-badge tar-badgecat-compression-badge

使用 tar 包

解压 tar 包

flate2-badge tar-badge cat-compression-badge

从当前工作目录中的压缩包 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 包

flate2-badge tar-badge cat-compression-badge

压缩 /var/log 目录内的内容到 archive.tar.gz 压缩包中。

创建一个用 GzEncodertar::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 包

flate2-badge tar-badge cat-compression-badge

循环遍历 Archive::entries。使用 Path::strip_prefix 移除指定的路径前缀(bundle/logs)。最终,通过 Entry::unpack 提取 tar::Entry(tar 包中的内容)。

use error_chain::error_chain; use std::fs::File; use std::path::PathBuf; use flate2::read::GzDecoder; use tar::Archive; error_chain! { foreign_links { Io(std::io::Error); StripPrefixError(::std::path::StripPrefixError); } } fn main() -> Result<()> { let file = File::open("archive.tar.gz")?; let mut archive = Archive::new(GzDecoder::new(file)); let prefix = "bundle/logs"; println!("Extracted the following files:"); archive .entries()? .filter_map(|e| e.ok()) .map(|mut entry| -> Result<PathBuf> { let path = entry.path()?.strip_prefix(prefix)?.to_owned(); entry.unpack(&path)?; Ok(path) }) .filter_map(|e| e.ok()) .for_each(|x| println!("> {}", x.display())); Ok(()) }

并发/并行

显式线程

实例名称Crates类别
生成短期线程crossbeam-badgecat-concurrency-badge
创建并发的数据管道crossbeam-badgecat-concurrency-badge
在两个线程间传递数据crossbeam-badgecat-concurrency-badge
保持全局可变状态lazy_static-badgecat-rust-patterns-badge
对所有 iso 文件的 SHA256 值并发求和threadpool-badge walkdir-badge num_cpus-badge ring-badgecat-concurrency-badgecat-filesystem-badge
将绘制分形的线程分派到线程池threadpool-badge num-badge num_cpus-badge image-badgecat-concurrency-badgecat-science-badgecat-rendering-badge

数据并行

实例名称Crates类别
并行改变数组中元素rayon-badgecat-concurrency-badge
并行测试集合中任意或所有的元素是否匹配给定断言rayon-badgecat-concurrency-badge
使用给定断言并行搜索项rayon-badgecat-concurrency-badge
对 vector 并行排序rayon-badge rand-badgecat-concurrency-badge
Map-reduce 并行计算rayon-badgecat-concurrency-badge
并行生成 jpg 缩略图rayon-badge glob-badge image-badgecat-concurrency-badgecat-filesystem-badge

显式线程

生成短期线程

crossbeam-badge cat-concurrency-badge

本实例使用 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-badge cat-concurrency-badge

下面的实例使用 crossbeamcrossbeam-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(); }

在两个线程间传递数据

crossbeam-badge cat-concurrency-badge

这个实例示范了在单生产者、单消费者(SPSC)环境中使用 crossbeam-channel。我们构建的生成短期线程实例中,使用 crossbeam::scopeScope::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-badge cat-rust-patterns-badge

使用 lazy_static 声明全局状态。lazy_static 创建了一个全局可用的 static ref,它需要 Mutex 来允许变化(请参阅 RwLock)。在 Mutex 的包裹下,保证了状态不能被多个线程同时访问,从而防止出现争用情况。必须获取 MutexGuard,方可读取或更改存储在 Mutex 中的值。

use error_chain::error_chain; use lazy_static::lazy_static; use std::sync::Mutex; error_chain!{ } lazy_static! { static ref FRUIT: Mutex<Vec<String>> = Mutex::new(Vec::new()); } fn insert(fruit: &str) -> Result<()> { let mut db = FRUIT.lock().map_err(|_| "Failed to acquire MutexGuard")?; db.push(fruit.to_string()); Ok(()) } fn main() -> Result<()> { insert("apple")?; insert("orange")?; insert("peach")?; { let db = FRUIT.lock().map_err(|_| "Failed to acquire MutexGuard")?; db.iter().enumerate().for_each(|(i, item)| println!("{}: {}", i, item)); } insert("grape")?; Ok(()) }

对所有 iso 文件的 SHA256 值并发求和

threadpool-badge num_cpus-badge walkdir-badge ring-badge cat-concurrency-badgecat-filesystem-badge

下面的实例计算了当前目录中每个扩展名为 iso 的文件的 SHA256 哈希值。线程池生成的线程数与使用 num_cpus::get 获取的系统内核数相等。Walkdir::new 遍历当前目录,并调用 execute 来执行读取和计算 SHA256 哈希值的操作。

use walkdir::WalkDir; use std::fs::File; use std::io::{BufReader, Read, Error}; use std::path::Path; use threadpool::ThreadPool; use std::sync::mpsc::channel; use ring::digest::{Context, Digest, SHA256}; // Verify the iso extension fn is_iso(entry: &Path) -> bool { match entry.extension() { Some(e) if e.to_string_lossy().to_lowercase() == "iso" => true, _ => false, } } fn compute_digest<P: AsRef<Path>>(filepath: P) -> Result<(Digest, P), Error> { let mut buf_reader = BufReader::new(File::open(&filepath)?); let mut context = Context::new(&SHA256); let mut buffer = [0; 1024]; loop { let count = buf_reader.read(&mut buffer)?; if count == 0 { break; } context.update(&buffer[..count]); } Ok((context.finish(), filepath)) } fn main() -> Result<(), Error> { let pool = ThreadPool::new(num_cpus::get()); let (tx, rx) = channel(); for entry in WalkDir::new("/home/user/Downloads") .follow_links(true) .into_iter() .filter_map(|e| e.ok()) .filter(|e| !e.path().is_dir() && is_iso(e.path())) { let path = entry.path().to_owned(); let tx = tx.clone(); pool.execute(move || { let digest = compute_digest(path); tx.send(digest).expect("Could not send data!"); }); } drop(tx); for t in rx.iter() { let (sha, path) = t?; println!("{:?} {:?}", sha, path); } Ok(()) }

将绘制分形的线程分派到线程池

threadpool-badge num-badge num_cpus-badge image-badge cat-concurrency-badgecat-science-badgecat-rendering-badge

此实例通过从朱莉娅集绘制分形来生成图像,该集合具有用于分布式计算的线程池。

使用 ImageBuffer::new 为指定宽度和高度的输出图像分配内存,Rgb::from_channels 信道则计算输出图像的 RGB 像素值。使用 ThreadPool 创建线程池,线程池中的线程数量和使用 num_cpus::get 获取的系统内核数相等。ThreadPool::execute 将每个像素作为单独的作业接收。

mpsc::channel 信道接收作业,Receiver::recv 接收器则检索作业。ImageBuffer::put_pixel 处理数据,设置像素颜色。最后,ImageBuffer::save 将图像存储为 output.png

use error_chain::error_chain; use std::sync::mpsc::{channel, RecvError}; use threadpool::ThreadPool; use num::complex::Complex; use image::{ImageBuffer, Pixel, Rgb}; error_chain! { foreign_links { MpscRecv(RecvError); Io(std::io::Error); } } // 将强度值转换为 RGB 值的函数 // 基于 http://www.efg2.com/Lab/ScienceAndEngineering/Spectra.htm fn wavelength_to_rgb(wavelength: u32) -> Rgb<u8> { let wave = wavelength as f32; let (r, g, b) = match wavelength { 380..=439 => ((440. - wave) / (440. - 380.), 0.0, 1.0), 440..=489 => (0.0, (wave - 440.) / (490. - 440.), 1.0), 490..=509 => (0.0, 1.0, (510. - wave) / (510. - 490.)), 510..=579 => ((wave - 510.) / (580. - 510.), 1.0, 0.0), 580..=644 => (1.0, (645. - wave) / (645. - 580.), 0.0), 645..=780 => (1.0, 0.0, 0.0), _ => (0.0, 0.0, 0.0), }; let factor = match wavelength { 380..=419 => 0.3 + 0.7 * (wave - 380.) / (420. - 380.), 701..=780 => 0.3 + 0.7 * (780. - wave) / (780. - 700.), _ => 1.0, }; let (r, g, b) = (normalize(r, factor), normalize(g, factor), normalize(b, factor)); Rgb::from_channels(r, g, b, 0) } // 将茱莉亚集距离映射为强度值 fn julia(c: Complex<f32>, x: u32, y: u32, width: u32, height: u32, max_iter: u32) -> u32 { let width = width as f32; let height = height as f32; let mut z = Complex { // scale and translate the point to image coordinates re: 3.0 * (x as f32 - 0.5 * width) / width, im: 2.0 * (y as f32 - 0.5 * height) / height, }; let mut i = 0; for t in 0..max_iter { if z.norm() >= 2.0 { break; } z = z * z + c; i = t; } i } // 规格 RGB 颜色值范围内的强度值 fn normalize(color: f32, factor: f32) -> u8 { ((color * factor).powf(0.8) * 255.) as u8 } fn main() -> Result<()> { let (width, height) = (1920, 1080); let mut img = ImageBuffer::new(width, height); let iterations = 300; let c = Complex::new(-0.8, 0.156); let pool = ThreadPool::new(num_cpus::get()); let (tx, rx) = channel(); for y in 0..height { let tx = tx.clone(); pool.execute(move || for x in 0..width { let i = julia(c, x, y, width, height, iterations); let pixel = wavelength_to_rgb(380 + i * 400 / iterations); tx.send((x, y, pixel)).expect("Could not send data!"); }); } for _ in 0..(width * height) { let (x, y, pixel) = rx.recv()?; img.put_pixel(x, y, pixel); } let _ = img.save("output.png")?; Ok(()) }

数据并行

并行改变数组中元素

rayon-badge cat-concurrency-badge

下面的实例使用了 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-badge cat-concurrency-badge

这个实例示范如何使用 rayon::anyrayon::all 方法,这两个方法是分别与 std::anystd::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-badge cat-concurrency-badge

下面的实例使用 rayon::find_anypar_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 并行排序

rayon-badge rand-badge cat-concurrency-badge

本实例对字符串 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-badge cat-concurrency-badge

此实例使用 rayon::filterrayon::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 缩略图

rayon-badge glob-badge image-badge cat-concurrency-badge cat-filesystem-badge

本实例为当前目录中的所有 .jpg 图像文件生成缩略图,然后将生成的缩略图保存在一个名为 thumbnails 的新文件夹中。

glob::glob_with 在当前目录中查找 jpeg 图像文件,rayon 通过 par_iter 方法调用 DynamicImage::resize,并行地调整图像大小。

use error_chain::error_chain; use std::path::Path; use std::fs::create_dir_all; use error_chain::ChainedError; use glob::{glob_with, MatchOptions}; use image::{FilterType, ImageError}; use rayon::prelude::*; error_chain! { foreign_links { Image(ImageError); Io(std::io::Error); Glob(glob::PatternError); } } fn main() -> Result<()> { let options: MatchOptions = Default::default(); let files: Vec<_> = glob_with("*.jpg", options)? .filter_map(|x| x.ok()) .collect(); if files.len() == 0 { error_chain::bail!("No .jpg files found in current directory"); } let thumb_dir = "thumbnails"; create_dir_all(thumb_dir)?; println!("Saving {} thumbnails into '{}'...", files.len(), thumb_dir); let image_failures: Vec<_> = files .par_iter() .map(|path| { make_thumbnail(path, thumb_dir, 300) .map_err(|e| e.chain_err(|| path.display().to_string())) }) .filter_map(|x| x.err()) .collect(); image_failures.iter().for_each(|x| println!("{}", x.display_chain())); println!("{} thumbnails saved successfully", files.len() - image_failures.len()); Ok(()) } fn make_thumbnail<PA, PB>(original: PA, thumb_dir: PB, longest_edge: u32) -> Result<()> where PA: AsRef<Path>, PB: AsRef<Path>, { let img = image::open(original.as_ref())?; let file_path = thumb_dir.as_ref().join(original); Ok(img.resize(longest_edge, longest_edge, FilterType::Nearest) .save(file_path)?) }

密码学

散列(哈希)

实例名称Crates类别
计算文件的 SHA-256 摘要ring-badge data-encoding-badgecat-cryptography-badge
使用 HMAC 摘要对消息进行签名和验证ring-badgecat-cryptography-badge

加密

实例名称Crates类别
使用 PBKDF2 对密码进行加密(salt)和散列(hash)运算ring-badge data-encoding-badgecat-cryptography-badge

散列(哈希)

计算文件的 SHA-256 摘要

ring-badge data-encoding-badge cat-cryptography-badge

如下实例中,先创建文件,写入一些数据。然后使用 digest::Context 计算文件内容的 SHA-256 摘要 digest::Digest

use error_chain::error_chain; use data_encoding::HEXUPPER; use ring::digest::{Context, Digest, SHA256}; use std::fs::File; use std::io::{BufReader, Read, Write}; error_chain! { foreign_links { Io(std::io::Error); Decode(data_encoding::DecodeError); } } fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest> { let mut context = Context::new(&SHA256); let mut buffer = [0; 1024]; loop { let count = reader.read(&mut buffer)?; if count == 0 { break; } context.update(&buffer[..count]); } Ok(context.finish()) } fn main() -> Result<()> { let path = "file.txt"; let mut output = File::create(path)?; write!(output, "We will generate a digest of this text")?; let input = File::open(path)?; let reader = BufReader::new(input); let digest = sha256_digest(reader)?; println!("SHA-256 digest is {}", HEXUPPER.encode(digest.as_ref())); Ok(()) }

使用 HMAC 摘要对消息进行签名和验证

ring-badge cat-cryptography-badge

使用 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)运算

ring-badge data-encoding-badge cat-cryptography-badge

对于通过 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-badgecat-no-std-badge

位域

定义并操作位域风格的类型

bitflags-badge cat-no-std-badge

如下实例在 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 数据库rusqlite-badgecat-database-badge
数据插入和查询rusqlite-badgecat-database-badge
事务处理rusqlite-badgecat-database-badge

使用 Postgres

实例名称Crates类别
Postgres 数据库中创建表postgres-badgecat-database-badge
数据插入和查询postgres-badgecat-database-badge
数据聚合postgres-badgecat-database-badge

SQLite

创建 SQLite 数据库

rusqlite-badge cat-database-badge

使用 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(()) }

数据插入和查询

rusqlite-badge cat-database-badge

Connection::open 将打开在前一章节实例中创建的数据库 cats 的连接。下面的实例使用 Connectionexecute 方法将数据插入 cat_colorscats 两张表中。首先,将数据插入到 cat_colors 表中。随后,使用 Connectionlast_insert_rowid 方法来获取 cat_colors 表最后插入记录的 id。当向 cats 表中插入数据时,使用此 id。然后,使用 prepare 方法准备执行 select 查询操作,该方法提供 statement 结构体。最后,使用 statementquery_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(()) }

事务处理

rusqlite-badge cat-database-badge

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-badge cat-database-badge

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(()) }

数据插入和查询

postgres-badge cat-database-badge

下述实例中使用 Clientexecute 方法将数据插入到 author 表中。然后,使用 Clientquery 方法查询 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(()) }

数据聚合

postgres-badge cat-database-badge

下述实例按照降序列出了美国纽约州现代艺术博物馆数据库中首批 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类别
测量运行时间std-badgecat-time-badge
执行日期检查和时间计算chrono-badgecat-date-and-time-badge
时间的时区转换chrono-badgecat-date-and-time-badge

解析与显示

实例名称Crates类别
检查日期和时间chrono-badgecat-date-and-time-badge
日期和 UNIX 时间戳的互相转换chrono-badgecat-date-and-time-badge
日期和时间的格式化显示chrono-badgecat-date-and-time-badge
将字符串解析为 DateTime 结构体chrono-badgecat-date-and-time-badge

期间和计算

测量运行时间

std-badge cat-time-badge

测量从 time::Instant::now 开始运行的时间 time::Instant::elapsed

调用 time::Instant::elapsed 将返回 time::Duration,我们将在实例末尾打印该时间。此方法不会更改或者重置 time::Instant 对象。

use std::time::{Duration, Instant}; use std::thread; fn expensive_function() { thread::sleep(Duration::from_secs(1)); } fn main() { let start = Instant::now(); expensive_function(); let duration = start.elapsed(); println!("Time elapsed in expensive_function() is: {:?}", duration); }

执行日期检查和时间计算

chrono-badge cat-date-and-time-badge

使用 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."), } }

时间的时区转换

chrono-badge cat-date-and-time-badge

使用 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)); }

解析与显示

检查日期和时间

chrono-badge cat-date-and-time-badge

通过 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 时间戳的互相转换

chrono-badge cat-date-and-time-badge

使用 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); }

日期和时间的格式化显示

chrono-badge cat-date-and-time-badge

使用 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 结构体

chrono-badge cat-date-and-time-badge

熟悉的时间格式 RFC 2822RFC 3339,以及自定义时间格式,通常用字符串表达。要将这些字符串解析为 DateTime 结构体,可以分别用 DateTime::parse_from_rfc2822DateTime::parse_from_rfc3339,以及 DateTime::parse_from_str

可以在 chrono::format::strftime 中找到适用于 DateTime::parse_from_str 的转义序列。注意:DateTime::parse_from_str 要求这些 DateTime 结构体必须是可创建的,以便它唯一地标识日期和时间。要解析不带时区的日期和时间,请使用 NaiveDateNaiveTime,以及 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类别
记录调试信息到控制台log-badge env_logger-badgecat-debugging-badge
记录错误信息到控制台log-badge env_logger-badgecat-debugging-badge
记录信息时,用标准输出 stdout 替换标准错误 stderrlog-badge env_logger-badgecat-debugging-badge
使用自定义日志记录器记录信息log-badgecat-debugging-badge
记录到 Unix 系统日志log-badge syslog-badgecat-debugging-badge

日志配置

实例名称Crates类别
启用每个模块的日志级别log-badge env_logger-badgecat-debugging-badge
用自定义环境变量设置日志记录log-badge env_logger-badgecat-debugging-badge
在日志信息中包含时间戳log-badge env_logger-badge chrono-badgecat-debugging-badge
将信息记录到自定义位置log-badge log4rs-badgecat-debugging-badge

版本控制

实例名称Crates类别
解析并递增版本字符串semver-badgecat-config-badge
解析复杂的版本字符串semver-badgecat-config-badge
检查给定版本是否为预发布版本semver-badgecat-config-badge
查询适配给定范围的最新版本semver-badgecat-config-badge
检查外部命令的版本兼容性semver-badgecat-text-processing-badge cat-os-badge

构建时

实例名称Crates类别
编译并静态链接到绑定的 C 语言库cc-badgecat-development-tools-badge
编译并静态链接到绑定的 C++ 语言库cc-badgecat-development-tools-badge
编译 C 语言库时自定义设置cc-badgecat-development-tools-badge

调试工具

日志信息

实例名称Crates类别
记录调试信息到控制台log-badge env_logger-badgecat-debugging-badge
记录错误信息到控制台log-badge env_logger-badgecat-debugging-badge
记录信息时,用标准输出 stdout 替换标准错误 stderrlog-badge env_logger-badgecat-debugging-badge
使用自定义日志记录器记录信息log-badgecat-debugging-badge
记录到 Unix 系统日志log-badge syslog-badgecat-debugging-badge

日志配置

实例名称Crates类别
启用每个模块的日志级别log-badge env_logger-badgecat-debugging-badge
用自定义环境变量设置日志记录log-badge env_logger-badgecat-debugging-badge
在日志信息中包含时间戳log-badge env_logger-badge chrono-badgecat-debugging-badge
将信息记录到自定义位置log-badge log4rs-badgecat-debugging-badge

日志信息

记录调试信息到控制台

log-badge env_logger-badge cat-debugging-badge

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-badge env_logger-badge cat-debugging-badge

正确的错误处理会将异常视为错误。下述实例中,通过 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

log-badge env_logger-badge cat-debugging-badge

使用 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"); }

使用自定义日志记录器记录信息

log-badge cat-debugging-badge

本实例实现一个打印到 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 系统日志

log-badge syslog-badge cat-debugging-badge

本实例实现将信息记录到 UNIX syslog。使用 syslog::init 初始化记录器后端。syslog::Facility 记录提交日志项分类的程序,log::LevelFilter 表示欲记录日志的等级,Option<&str> 定义应用程序名称(可选)。

#[cfg(target_os = "linux")] #[cfg(target_os = "linux")] use syslog::{Facility, Error}; #[cfg(target_os = "linux")] fn main() -> Result<(), Error> { syslog::init(Facility::LOG_USER, log::LevelFilter::Debug, Some("My app name"))?; log::debug!("this is a debug {}", "message"); log::error!("this is an error!"); Ok(()) } #[cfg(not(target_os = "linux"))] fn main() { println!("So far, only Linux systems are supported."); }

日志配置

启用每个模块的日志级别

log-badge env_logger-badge cat-debugging-badge

创建两个模块: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 的日志等级设置为 infodebug

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

用自定义环境变量设置日志记录

log-badge env_logger-badge cat-debugging-badge

Builder 配置日志记录。

Builder::parseRUST_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"); }

在日志信息中包含时间戳

log-badge env_logger-badge chrono-badge cat-debugging-badge

使用 Builder 创建自定义记录器配置。每个日志项调用 Local::now 以获取本地时区中的当前 DateTime,并使用 DateTime::formatstrftime::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

将信息记录到自定义位置

log-badge log4rs-badge cat-debugging-badge

log4rs 将日志输出配置到自定义位置。log4rs 可以使用外部 YAML 文件或生成器配置。

使用文件附加器 log4rs::append::file::FileAppender 创建日志配置,文件附加器定义日志记录的目标位置。日志配置使用 log4rs::encode::pattern 中的自定义模式进行编码,将配置项分配给 log4rs::config::Config,并设置默认的日志等级 log::LevelFilter

use error_chain::error_chain; use log::LevelFilter; use log4rs::append::file::FileAppender; use log4rs::encode::pattern::PatternEncoder; use log4rs::config::{Appender, Config, Root}; error_chain! { foreign_links { Io(std::io::Error); LogConfig(log4rs::config::Errors); SetLogger(log::SetLoggerError); } } fn main() -> Result<()> { let logfile = FileAppender::builder() .encoder(Box::new(PatternEncoder::new("{l} - {m}\n"))) .build("log/output.log")?; let config = Config::builder() .appender(Appender::builder().build("logfile", Box::new(logfile))) .build(Root::builder() .appender("logfile") .build(LevelFilter::Info))?; log4rs::init_config(config)?; log::info!("Hello, world!"); Ok(()) }

版本控制

解析并递增版本字符串

semver-badge cat-config-badge

使用 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(()) }

解析复杂的版本字符串

semver-badge cat-config-badge

使用 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(()) }

检查给定版本是否为预发布版本

semver-badge cat-config-badge

给定两个版本,使用 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(()) }

查询适配给定范围的最新版本

semver-badge cat-config-badge

给定一个版本字符串 &str 的列表,查找最新的语义化版本 semver::Versionsemver::VersionReqVersionReq::matches 过滤列表,也可以展示语义化版本 semver 的预发布参数设置。

use error_chain::error_chain; use semver::{Version, VersionReq}; error_chain! { foreign_links { SemVer(semver::SemVerError); SemVerReq(semver::ReqParseError); } } fn find_max_matching_version<'a, I>(version_req_str: &str, iterable: I) -> Result<Option<Version>> where I: IntoIterator<Item = &'a str>, { let vreq = VersionReq::parse(version_req_str)?; Ok( iterable .into_iter() .filter_map(|s| Version::parse(s).ok()) .filter(|s| vreq.matches(s)) .max(), ) } fn main() -> Result<()> { assert_eq!( find_max_matching_version("<= 1.0.0", vec!["0.9.0", "1.0.0", "1.0.1"])?, Some(Version::parse("1.0.0")?) ); assert_eq!( find_max_matching_version( ">1.2.3-alpha.3", vec![ "1.2.3-alpha.3", "1.2.3-alpha.4", "1.2.3-alpha.10", "1.2.3-beta.4", "3.4.5-alpha.9", ] )?, Some(Version::parse("1.2.3-beta.4")?) ); Ok(()) }

检查外部命令的版本兼容性

semver-badge cat-text-processing-badge cat-os-badge

本实例使用 Command 模块运行命令 git --version,然后使用 Version::parse 将版本号解析为语义化版本 semver::VersionVersionReq::matchessemver::VersionReq 与解析的语义化版本进行比较。最终,命令输出类似于“git version x.y.z”。

use error_chain::error_chain; use std::process::Command; use semver::{Version, VersionReq}; error_chain! { foreign_links { Io(std::io::Error); Utf8(std::string::FromUtf8Error); SemVer(semver::SemVerError); SemVerReq(semver::ReqParseError); } } fn main() -> Result<()> { let version_constraint = "> 1.12.0"; let version_test = VersionReq::parse(version_constraint)?; let output = Command::new("git").arg("--version").output()?; if !output.status.success() { error_chain::bail!("Command executed with failing error code"); } let stdout = String::from_utf8(output.stdout)?; let version = stdout.split(" ").last().ok_or_else(|| { "Invalid command output" })?; let parsed_version = Version::parse(version)?; if !version_test.matches(&parsed_version) { error_chain::bail!("Command version lower than minimum supported version (found {}, need {})", parsed_version, version_constraint); } Ok(()) }

构建工具

本节介绍在编译 crate 源代码之前运行的“构建时”工具或代码。按照惯例,构建时代码存放在 build.rs 文件,通常称为“构建脚本”。常见的用例包括:Rust 代码生成、绑定的 C/C++/asm 代码的编译。要获取更多信息,请阅读(Cargo 手册 中文版) 的构建脚本文档

编译并静态链接到绑定的 C 语言库

cc-badge cat-development-tools-badge

为了适应项目中需要混合 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++ 语言库

cc-badge cat-development-tools-badge

链接绑定的 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-badge cat-development-tools-badge

使用 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 编码)字符串percent-encoding-badgecat-encoding-badge
将字符串编码为 application/x-www-form-urlencodedurl-badgecat-encoding-badge
编码和解码十六进制data-encoding-badgecat-encoding-badge
编码和解码 base64base64-badgecat-encoding-badge

CSV 处理

实例名称Crates类别
读取 CSV 记录csv-badgecat-encoding-badge
读取有不同分隔符的 CSV 记录csv-badgecat-encoding-badge
筛选匹配断言的 CSV 记录csv-badgecat-encoding-badge
用 Serde 处理无效的 CSV 数据csv-badge serde-badgecat-encoding-badge
将记录序列化为 CSVcsv-badgecat-encoding-badge
用 Serde 将记录序列化为 CSVcsv-badge serde-badgecat-encoding-badge
转换 CSV 文件的列csv-badge serde-badgecat-encoding-badge

结构化数据

实例名称Crates类别
对非结构化 JSON 序列化和反序列化serde-json-badgecat-encoding-badge
反序列化 TOML 配置文件toml-badgecat-encoding-badge
以小端模式(低位模式)字节顺序读写整数byteorder-badgecat-encoding-badge

字符集

百分比编码(URL 编码)字符串

percent-encoding-badge cat-encoding-badge

使用 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

url-badge cat-encoding-badge

如下实例使用 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-badge cat-encoding-badge

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

base64-badge cat-encoding-badge

使用 encode 将字节切片编码为 base64 字符串,对 base64 字符串解码使用 decode

use error_chain::error_chain; use std::str; use base64::{encode, decode}; error_chain! { foreign_links { Base64(base64::DecodeError); Utf8Error(str::Utf8Error); } } fn main() -> Result<()> { let hello = b"hello rustaceans"; let encoded = encode(hello); let decoded = decode(&encoded)?; println!("origin: {}", str::from_utf8(hello)?); println!("base64 encoded: {}", encoded); println!("back to origin: {}", str::from_utf8(&decoded)?); Ok(()) }

CSV 处理

读取 CSV 记录

csv-badge cat-encoding-badge

将标准的 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 记录

csv-badge cat-encoding-badge

使用制表(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 记录

csv-badge cat-encoding-badge

仅仅 返回 data 中字段(field)与 query 匹配的的行。

use error_chain::error_chain; use std::io; error_chain!{ foreign_links { Io(std::io::Error); CsvError(csv::Error); } } fn main() -> Result<()> { let query = "CA"; let data = "\ City,State,Population,Latitude,Longitude Kenai,AK,7610,60.5544444,-151.2583333 Oakman,AL,,33.7133333,-87.3886111 Sandfort,AL,,32.3380556,-85.2233333 West Hollywood,CA,37031,34.0900000,-118.3608333"; let mut rdr = csv::ReaderBuilder::new().from_reader(data.as_bytes()); let mut wtr = csv::Writer::from_writer(io::stdout()); wtr.write_record(rdr.headers()?)?; for result in rdr.records() { let record = result?; if record.iter().any(|field| field == query) { wtr.write_record(&record)?; } } wtr.flush()?; Ok(()) }

免责声明:此实例改编自csv crate 教程

用 Serde 处理无效的 CSV 数据

csv-badge serde-badge cat-encoding-badge

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

csv-badge cat-encoding-badge

本实例展示了如何序列化 Rust 元组。csv::writer 支持从 Rust 类型到 CSV 记录的自动序列化。write_record 只写入包含字符串数据的简单记录。具有更复杂值(如数字、浮点和选项)的数据使用 serialize 进行序列化。因为 csv::writer 使用内部缓冲区,所以在完成时总是显式刷新 flush

use error_chain::error_chain; use std::io; error_chain! { foreign_links { CSVError(csv::Error); IOError(std::io::Error); } } fn main() -> Result<()> { let mut wtr = csv::Writer::from_writer(io::stdout()); wtr.write_record(&["Name", "Place", "ID"])?; wtr.serialize(("Mark", "Sydney", 87))?; wtr.serialize(("Ashley", "Dublin", 32))?; wtr.serialize(("Akshat", "Delhi", 11))?; wtr.flush()?; Ok(()) }

用 Serde 将记录序列化为 CSV

csv-badge serde-badge cat-encoding-badge

下面的实例展示如何使用 serde crate 将自定义结构体序列化为 CSV 记录。

use error_chain::error_chain; use serde::Serialize; use std::io; error_chain! { foreign_links { IOError(std::io::Error); CSVError(csv::Error); } } #[derive(Serialize)] struct Record<'a> { name: &'a str, place: &'a str, id: u64, } fn main() -> Result<()> { let mut wtr = csv::Writer::from_writer(io::stdout()); let rec1 = Record { name: "Mark", place: "Melbourne", id: 56}; let rec2 = Record { name: "Ashley", place: "Sydney", id: 64}; let rec3 = Record { name: "Akshat", place: "Delhi", id: 98}; wtr.serialize(rec1)?; wtr.serialize(rec2)?; wtr.serialize(rec3)?; wtr.flush()?; Ok(()) }

转换 CSV 文件的列

csv-badge serde-badge cat-encoding-badge

将包含颜色名称和十六进制颜色值的 CSV 文件转换为具有颜色名称和 rgb 颜色值的 CSV 文件。使用 csv crate 读写 csv 文件,使用 serde crate 对行输入字节进行反序列化,对行输出字节进行序列化。

详细请参阅 csv::Reader::deserializeserde::Deserialize,以及 std::str::FromStr

use error_chain::error_chain; use csv::{Reader, Writer}; use serde::{de, Deserialize, Deserializer}; use std::str::FromStr; error_chain! { foreign_links { CsvError(csv::Error); ParseInt(std::num::ParseIntError); CsvInnerError(csv::IntoInnerError<Writer<Vec<u8>>>); IO(std::fmt::Error); UTF8(std::string::FromUtf8Error); } } #[derive(Debug)] struct HexColor { red: u8, green: u8, blue: u8, } #[derive(Debug, Deserialize)] struct Row { color_name: String, color: HexColor, } impl FromStr for HexColor { type Err = Error; fn from_str(hex_color: &str) -> std::result::Result<Self, Self::Err> { let trimmed = hex_color.trim_matches('#'); if trimmed.len() != 6 { Err("Invalid length of hex string".into()) } else { Ok(HexColor { red: u8::from_str_radix(&trimmed[..2], 16)?, green: u8::from_str_radix(&trimmed[2..4], 16)?, blue: u8::from_str_radix(&trimmed[4..6], 16)?, }) } } } impl<'de> Deserialize<'de> for HexColor { fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; FromStr::from_str(&s).map_err(de::Error::custom) } } fn main() -> Result<()> { let data = "color_name,color red,#ff0000 green,#00ff00 blue,#0000FF periwinkle,#ccccff magenta,#ff00ff" .to_owned(); let mut out = Writer::from_writer(vec![]); let mut reader = Reader::from_reader(data.as_bytes()); for result in reader.deserialize::<Row>() { let res = result?; out.serialize(( res.color_name, res.color.red, res.color.green, res.color.blue, ))?; } let written = String::from_utf8(out.into_inner()?)?; assert_eq!(Some("magenta,255,0,255"), written.lines().last()); println!("{}", written); Ok(()) }

结构化数据

对非结构化 JSON 序列化和反序列化

serde-json-badge cat-encoding-badge

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-badge cat-encoding-badge

将一些 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-badge cat-encoding-badge

字节序 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 方法中对错误适当处理error-chain-badgecat-rust-patterns-badge
避免在错误转变过程中遗漏错误error-chain-badgecat-rust-patterns-badge
获取复杂错误场景的回溯error-chain-badgecat-rust-patterns-badge

处理错误变量

在 main 方法中对错误适当处理

error-chain-badge cat-rust-patterns-badge

处理尝试打开不存在的文件时发生的错误,是通过使用 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-badge cat-rust-patterns-badge

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), } } }

获取复杂错误场景的回溯

error-chain-badge cat-rust-patterns-badge

本实例展示了如何处理一个复杂的错误场景,并且打印出错误回溯。依赖于 chain_err,通过附加新的错误来扩展错误信息。从而可以展开错误堆栈,这样提供了更好的上下文来理解错误的产生原因。

下述代码尝试将值 256 反序列化为 u8。首先 Serde 产生错误,然后是 csv,最后是用户代码。

use error_chain::error_chain; use serde::Deserialize; use std::fmt; error_chain! { foreign_links { Reader(csv::Error); } } #[derive(Debug, Deserialize)] struct Rgb { red: u8, blue: u8, green: u8, } impl Rgb { fn from_reader(csv_data: &[u8]) -> Result<Rgb> { let color: Rgb = csv::Reader::from_reader(csv_data) .deserialize() .nth(0) .ok_or("Cannot deserialize the first CSV record")? .chain_err(|| "Cannot deserialize RGB color")?; Ok(color) } } impl fmt::UpperHex for Rgb { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let hexa = u32::from(self.red) << 16 | u32::from(self.blue) << 8 | u32::from(self.green); write!(f, "{:X}", hexa) } } fn run() -> Result<()> { let csv = "red,blue,green 102,256,204"; let rgb = Rgb::from_reader(csv.as_bytes()).chain_err(|| "Cannot read CSV data")?; println!("{:?} to hexadecimal #{:X}", rgb, rgb); Ok(()) } fn main() { if let Err(ref errors) = run() { eprintln!("Error level - description"); errors .iter() .enumerate() .for_each(|(index, error)| eprintln!("└> {} - {}", index, error)); if let Some(backtrace) = errors.backtrace() { eprintln!("{:?}", backtrace); } // In a real use case, errors should handled. For example: // ::std::process::exit(1); } }

错误回溯信息如下:

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类别
读取文件的字符串行std-badgecat-filesystem-badge
避免读取写入同一文件same_file-badgecat-filesystem-badge
使用内存映射随机访问文件memmap-badgecat-filesystem-badge

目录遍历

实例名称Crates类别
过去 24 小时内修改过的文件名std-badgecat-filesystem-badge cat-os-badge
查找给定路径的循环same_file-badgecat-filesystem-badge
递归查找重名文件walkdir-badgecat-filesystem-badge
使用给定断言递归查找所有文件walkdir-badgecat-filesystem-badge
跳过隐藏文件遍历目录walkdir-badgecat-filesystem-badge
在给定深度的目录,递归计算文件大小walkdir-badgecat-filesystem-badge
递归查找所有 png 文件glob-badgecat-filesystem-badge
忽略文件名大小写,使用给定模式查找所有文件glob-badgecat-filesystem-badge

文件读写

读取文件的字符串行

std-badge cat-filesystem-badge

我们向文件写入三行信息,然后使用 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-badge cat-filesystem-badge

对文件使用 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-badge cat-filesystem-badge

使用 memmap 创建文件的内存映射,并模拟文件的一些非序列读取。使用内存映射意味着您仅需索引一个切片,而不是使用 seek 方法来导航整个文件。

Mmap::map 函数假定内存映射后的文件没有被另一个进程同时更改,否则会出现竞态条件

use memmap::Mmap; use std::fs::File; use std::io::{Write, Error}; fn main() -> Result<(), Error> { write!(File::create("content.txt")?, "My hovercraft is full of eels!")?; let file = File::open("content.txt")?; let map = unsafe { Mmap::map(&file)? }; let random_indexes = [0, 1, 2, 19, 22, 10, 11, 29]; assert_eq!(&map[3..13], b"hovercraft"); let random_bytes: Vec<u8> = random_indexes.iter() .map(|&idx| map[idx]) .collect(); assert_eq!(&random_bytes[..], b"My loaf!"); Ok(()) }

目录遍历

过去 24 小时内修改过的文件名

std-badge cat-filesystem-badge

通过调用 env::current_dir 获取当前工作目录,然后通过 fs::read_dir 读取目录中的每个条目,通过 DirEntry::path 提取条目路径,以及通过通过 fs::Metadata 获取条目元数据。Metadata::modified 返回条目自上次更改以来的运行时间 SystemTime::elapsedDuration::as_secs 将时间转换为秒,并与 24 小时(24 * 60 * 60 秒)进行比较。Metadata::is_file 用于筛选出目录。

use error_chain::error_chain; use std::{env, fs}; error_chain! { foreign_links { Io(std::io::Error); SystemTimeError(std::time::SystemTimeError); } } fn main() -> Result<()> { let current_dir = env::current_dir()?; println!( "Entries modified in the last 24 hours in {:?}:", current_dir ); for entry in fs::read_dir(current_dir)? { let entry = entry?; let path = entry.path(); let metadata = fs::metadata(&path)?; let last_modified = metadata.modified()?.elapsed()?.as_secs(); if last_modified < 24 * 3600 && metadata.is_file() { println!( "Last modified: {:?} seconds, is read only: {:?}, size: {:?} bytes, filename: {:?}", last_modified, metadata.permissions().readonly(), metadata.len(), path.file_name().ok_or("No filename")? ); } } Ok(()) }

查找给定路径的循环

same_file-badge cat-filesystem-badge

使用 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") )) ); }

递归查找重名文件

walkdir-badge cat-filesystem-badge

在当前目录中递归查找重复的文件名,只打印一次。

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); } } }

使用给定断言递归查找所有文件

walkdir-badge cat-filesystem-badge

在当前目录中查找最近一天内修改的 JSON 文件。使用 follow_links 确保软链接(符号链接)像普通目录和文件一样被按照当前查找规则执行。

use error_chain::error_chain; use walkdir::WalkDir; error_chain! { foreign_links { WalkDir(walkdir::Error); Io(std::io::Error); SystemTime(std::time::SystemTimeError); } } fn main() -> Result<()> { for entry in WalkDir::new(".") .follow_links(true) .into_iter() .filter_map(|e| e.ok()) { let f_name = entry.file_name().to_string_lossy(); let sec = entry.metadata()?.modified()?; if f_name.ends_with(".json") && sec.elapsed()?.as_secs() < 86400 { println!("{}", f_name); } } Ok(()) }

跳过隐藏文件遍历目录

walkdir-badge cat-filesystem-badge

递归下行到子目录的过程中,使用 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-badge cat-filesystem-badge

通过 WalkDir::min_depthWalkDir::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 文件

glob-badge cat-filesystem-badge

递归地查找当前目录中的所有 PNG 文件。在本实例中,** 模式用于匹配当前目录及其所有子目录。

在路径任意部分使用 ** 模式,例如,/media/**/*.png 匹配 media 及其子目录中的所有 PNG 文件。

use error_chain::error_chain; use glob::glob; error_chain! { foreign_links { Glob(glob::GlobError); Pattern(glob::PatternError); } } fn main() -> Result<()> { for entry in glob("**/*.png")? { println!("{}", entry?.display()); } Ok(()) }

忽略文件名大小写,使用给定模式查找所有文件

glob-badge cat-filesystem-badge

/media/ 目录中查找与正则表达模式 img_[0-9]*.png 匹配的所有图像文件。

一个自定义 MatchOptions 结构体被传递给 glob_with 函数,使全局命令模式下不区分大小写,同时保持其他选项的默认值 Default

译者注:globglob 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 内核的数量num_cpus-badgecat-hardware-support-badge

处理器

检查逻辑 cpu 内核的数量

num_cpus-badge cat-hardware-support-badge

使用 [num_cpus::get] 显示当前机器中的逻辑 CPU 内核的数量。

fn main() { println!("Number of logical cores is {}", num_cpus::get()); }

内存管理

常量

实例名称Crates类别
声明延迟计算常量lazy_static-badgecat-caching-badge cat-rust-patterns-badge

常量

声明延迟计算常量

lazy_static-badge cat-caching-badge cat-rust-patterns-badge

声明延迟计算的常量 HashMapHashMap 将被计算一次,随后存储在全局静态(全局堆栈)引用。

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 端口std-badgecat-net-badge

服务器

监听未使用的 TCP/IP 端口

std-badge cat-net-badge

在本实例中,程序将监听显示在控制台上的端口,直到一个请求被发出。当将端口设置为 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类别
运行外部命令并处理 stdoutregex-badgecat-os-badge cat-text-processing-badge
运行传递 stdin 的外部命令,并检查错误代码regex-badgecat-os-badge cat-text-processing-badge
运行管道传输的外部命令std-badgecat-os-badge
将子进程的 stdout 和 stderr 重定向到同一个文件std-badgecat-os-badge
持续处理子进程的输出std-badgecat-os-badgecat-text-processing-badge
读取环境变量std-badgecat-os-badge

外部命令

运行外部命令并处理 stdout

regex-badge cat-os-badge cat-text-processing-badge

git log --oneline 作为外部命令 Command 运行,并使用 Regex 检查其 Output,以获取最后 5 次提交的哈希值和消息。

use error_chain::error_chain; use std::process::Command; use regex::Regex; error_chain!{ foreign_links { Io(std::io::Error); Regex(regex::Error); Utf8(std::string::FromUtf8Error); } } #[derive(PartialEq, Default, Clone, Debug)] struct Commit { hash: String, message: String, } fn main() -> Result<()> { let output = Command::new("git").arg("log").arg("--oneline").output()?; if !output.status.success() { error_chain::bail!("Command executed with failing error code"); } let pattern = Regex::new(r"(?x) ([0-9a-fA-F]+) # 提交的哈希值 (.*) # 提交信息")?; String::from_utf8(output.stdout)? .lines() .filter_map(|line| pattern.captures(line)) .map(|cap| { Commit { hash: cap[1].to_string(), message: cap[2].trim().to_string(), } }) .take(5) .for_each(|x| println!("{:?}", x)); Ok(()) }

运行传递 stdin 的外部命令,并检查错误代码

std-badge cat-os-badge

使用外部命令 Command 打开 python 解释器,并传递一条 python 语句供其执行,然后解析语句的输出结构体 Output

use error_chain::error_chain; use std::collections::HashSet; use std::io::Write; use std::process::{Command, Stdio}; error_chain!{ errors { CmdError } foreign_links { Io(std::io::Error); Utf8(std::string::FromUtf8Error); } } fn main() -> Result<()> { let mut child = Command::new("python").stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; child.stdin .as_mut() .ok_or("Child process stdin has not been captured!")? .write_all(b"import this; copyright(); credits(); exit()")?; let output = child.wait_with_output()?; if output.status.success() { let raw_output = String::from_utf8(output.stdout)?; let words = raw_output.split_whitespace() .map(|s| s.to_lowercase()) .collect::<HashSet<_>>(); println!("Found {} unique words:", words.len()); println!("{:#?}", words); Ok(()) } else { let err = String::from_utf8(output.stderr)?; error_chain::bail!("External command failed:\n {}", err) } }

运行管道传输的外部命令

std-badge cat-os-badge

显示当前工作目录中前 10 大的文件和子目录,它等同于运行: du -ah . | sort -hr | head -n 10

每个命令 Command 代表一个进程,子进程的输出是通过父进程和子进程之间的管道 Stdio::piped 捕获的。

use error_chain::error_chain; use std::process::{Command, Stdio}; error_chain! { foreign_links { Io(std::io::Error); Utf8(std::string::FromUtf8Error); } } fn main() -> Result<()> { let directory = std::env::current_dir()?; let mut du_output_child = Command::new("du") .arg("-ah") .arg(&directory) .stdout(Stdio::piped()) .spawn()?; if let Some(du_output) = du_output_child.stdout.take() { let mut sort_output_child = Command::new("sort") .arg("-hr") .stdin(du_output) .stdout(Stdio::piped()) .spawn()?; du_output_child.wait()?; if let Some(sort_output) = sort_output_child.stdout.take() { let head_output_child = Command::new("head") .args(&["-n", "10"]) .stdin(sort_output) .stdout(Stdio::piped()) .spawn()?; let head_stdout = head_output_child.wait_with_output()?; sort_output_child.wait()?; println!( "Top 10 biggest files and directories in '{}':\n{}", directory.display(), String::from_utf8(head_stdout.stdout).unwrap() ); } } Ok(()) }

将子进程的 stdout 和 stderr 重定向到同一个文件

std-badge cat-os-badge

生成子进程并将 stdoutstderr 重定向到同一个文件。它遵循与运行管道传输的外部命令相同的思想,但是 process::Stdio 会将输出写入指定的文件。对 stdoutstderr 而言,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(()) }

持续处理子进程的输出

std-badge cat-os-badge

运行外部命令并处理-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-badge cat-os-badge

通过 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 范数ndarray-badgecat-science-badge
Vector 比较ndarray-badgecat-science-badge
矩阵相加ndarray-badgecat-science-badge
矩阵相乘ndarray-badgecat-science-badge
标量、vector、矩阵相乘ndarray-badgecat-science-badge
矩阵求逆nalgebra-badgecat-science-badge
(反)序列化矩阵ndarray-badgecat-science-badge

三角学

实例名称Crates类别
计算三角形的边长std-badgecat-science-badge
验证正切(tan)等于正弦(sin)除以余弦(cos)std-badgecat-science-badge
地球上两点之间的距离std-badgecat-science-badge

复数

实例名称Crates类别
创建复数num-badgecat-science-badge
复数相加num-badgecat-science-badge
复数的数学函数num-badgecat-science-badge

统计学

实例名称Crates类别
集中趋势度量std-badgecat-science-badge
计算标准偏差std-badgecat-science-badge

其它数学计算

实例名称Crates类别
大数num-badgecat-science-badge

数学

线性代数

实例名称Crates类别
Vector 范数ndarray-badgecat-science-badge
Vector 比较ndarray-badgecat-science-badge
矩阵相加ndarray-badgecat-science-badge
矩阵相乘ndarray-badgecat-science-badge
标量、vector、矩阵相乘ndarray-badgecat-science-badge
矩阵求逆nalgebra-badgecat-science-badge
(反)序列化矩阵ndarray-badgecat-science-badge

三角学

实例名称Crates类别
计算三角形的边长std-badgecat-science-badge
验证正切(tan)等于正弦(sin)除以余弦(cos)std-badgecat-science-badge
地球上两点之间的距离std-badgecat-science-badge

复数

实例名称Crates类别
创建复数num-badgecat-science-badge
复数相加num-badgecat-science-badge
复数的数学函数num-badgecat-science-badge

统计学

实例名称Crates类别
集中趋势度量std-badgecat-science-badge
计算标准偏差std-badgecat-science-badge

其它数学计算

实例名称Crates类别
大数num-badgecat-science-badge

线性代数

矩阵相加

ndarray-badge cat-science-badge

使用 ndarray::arr2 创建两个二维(2-D)矩阵,并按元素方式求和。

注意:sum 的计算方式为 let sum = &a + &b,借用 & 运算符获得 ab 的引用,可避免销毁他们,使它们可以稍后显示。这样,就创建了一个包含其和的新数组。

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-badge cat-science-badge

使用 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-badge cat-science-badge

使用 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-badge

ndarray crate 支持多种创建数组的方法——此实例使用 fromstd::Vec 创建数组 ndarray::Array。然后,对数组以元素方式求和。

下面的实例按元素方式比较两个浮点型 vector。浮点数的存储通常不精确,因此很难进行精确的比较。但是,approx crate 中的 assert_abs_diff_eq! 宏允许方便地比较浮点型元素。要将 approxndarray 两个 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 范数

ndarray-badge

这个实例展示了 Array1 类型、ArrayView1 类型、fold 方法,以及 dot 方法在计算给定 vector 的 l1l2 范数时的用法。 + l2_norm 函数是两者中较简单的,它计算一个 vector 与自身的点积(dot product,数量积)的平方根。 + l1_norm 函数通过 fold 运算来计算元素的绝对值(也可以通过 x.mapv(f64::abs).scalar_sum() 执行,但是会为 mapv 的结果分配一个新的数组)。

请注意:l1_norml2_norm 都采用 ArrayView1 类型。这个实例考虑了 vector 范数,所以范数函数只需要接受一维视图(ArrayView1)。虽然函数可以使用类型为 &Array1<f64> 的参数,但这将要求调用方引用拥有所有权的数组,这比访问视图更为严格(因为视图可以从任意数组或视图创建,而不仅仅是从拥有所有权的数组创建)。

ArrayArrayView 都是 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-badge cat-science-badge

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!"); } } }

(反)序列化矩阵

ndarray-badge cat-science-badge

本实例实现将矩阵序列化为 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(()) }

三角学

计算三角形的边长

std-badge cat-science-badge

计算直角三角形斜边的长度,其中斜边的角度为 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)

std-badge cat-science-badge

验证 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); }

地球上两点之间的距离

std-badge

默认情况下,Rust 提供了数学上的浮点数方法,例如:三角函数、平方根、弧度和度数之间的转换函数等。

下面的实例使用半正矢公式计算地球上两点之间的距离(以公里为单位)。两个点用一对经纬度表示,然后,to_radians 将它们转换为弧度。sincospowi 以及 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-badge cat-science-badge

创建类型 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); }

复数相加

num-badge cat-science-badge

对复数执行数学运算与对内置类型执行数学运算是一样的:计算的数字必须是相同的类型(如浮点数或整数)。

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); }

复数的数学函数

num-badge cat-science-badge

在与其他数学函数交互时,复数有一系列有趣的特性,尤其是和自然常数 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 }

统计学

集中趋势度量

std-badge cat-science-badge

本节实例计算 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); }

计算标准偏差

std-badge cat-science-badge

本实例计算一组测量值的标准偏差和 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); }

其它数学计算

大数

num-badge cat-science-badge

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类别
验证并提取电子邮件登录信息regex-badge lazy_static-badgecat-text-processing-badge
从文本提取标签元素唯一的列表regex-badge lazy_static-badgecat-text-processing-badge
从文本提取电话号码regex-badgecat-text-processing-badge
通过匹配多个正则表达式来筛选日志文件regex-badgecat-text-processing-badge
文本模式替换regex-badge lazy_static-badgecat-text-processing-badge

字符串解析

实例名称Crates类别
收集 Unicode 字符unicode-segmentation-badgecat-encoding-badge
自定义结构体并实现 FromStr traitstd-badgecat-text-processing-badge

正则表达式

验证并提取电子邮件登录信息

regex-badge lazy_static-badge cat-text-processing-badge

验证电子邮件地址的格式是否正确,并提取 @ 符号之前的所有内容。

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); }

从文本提取标签元素唯一的列表

regex-badge lazy_static-badge cat-text-processing-badge

本实例展示从文本中提取、排序和去除标签列表的重复元素。

这里给出的标签正则表达式只捕获以字母开头的拉丁语标签,完整的 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-badge cat-text-processing-badge

使用 Regex::captures_iter 处理一个文本字符串,以捕获多个电话号码。这里的例子中是美国电话号码格式。

use error_chain::error_chain; use regex::Regex; use std::fmt; error_chain!{ foreign_links { Regex(regex::Error); Io(std::io::Error); } } struct PhoneNumber<'a> { area: &'a str, exchange: &'a str, subscriber: &'a str, } impl<'a> fmt::Display for PhoneNumber<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "1 ({}) {}-{}", self.area, self.exchange, self.subscriber) } } fn main() -> Result<()> { let phone_text = " +1 505 881 9292 (v) +1 505 778 2212 (c) +1 505 881 9297 (f) (202) 991 9534 Alex 5553920011 1 (800) 233-2010 1.299.339.1020"; let re = Regex::new( r#"(?x) (?:\+?1)? # 国家代码,可选项 [\s\.]? (([2-9]\d{2})|\(([2-9]\d{2})\)) # 地区代码 [\s\.\-]? ([2-9]\d{2}) # 交换代码 [\s\.\-]? (\d{4}) # 用户号码"#, )?; let phone_numbers = re.captures_iter(phone_text).filter_map(|cap| { let groups = (cap.get(2).or(cap.get(3)), cap.get(4), cap.get(5)); match groups { (Some(area), Some(ext), Some(sub)) => Some(PhoneNumber { area: area.as_str(), exchange: ext.as_str(), subscriber: sub.as_str(), }), _ => None, } }); assert_eq!( phone_numbers.map(|m| m.to_string()).collect::<Vec<_>>(), vec![ "1 (505) 881-9292", "1 (505) 778-2212", "1 (505) 881-9297", "1 (202) 991-9534", "1 (555) 392-0011", "1 (800) 233-2010", "1 (299) 339-1020", ] ); Ok(()) }

通过匹配多个正则表达式来筛选日志文件

regex-badge cat-text-processing-badge

读取名为 application.log 的文件,并且只输出包含下列内容的行:“version X.X.X”、端口为 443 的 IP 地址(如 “192.168.0.1:443”)、特定警告。

正则表达集构造器 regex::RegexSetBuilder 构建了正则表达式集 regex::RegexSet。由于反斜杠在正则表达式中非常常见,因此使用原始字符串字面量可以使它们更具可读性。

use error_chain::error_chain; use std::fs::File; use std::io::{BufReader, BufRead}; use regex::RegexSetBuilder; error_chain! { foreign_links { Io(std::io::Error); Regex(regex::Error); } } fn main() -> Result<()> { let log_path = "application.log"; let buffered = BufReader::new(File::open(log_path)?); let set = RegexSetBuilder::new(&[ r#"version "\d\.\d\.\d""#, r#"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:443"#, r#"warning.*timeout expired"#, ]).case_insensitive(true) .build()?; buffered .lines() .filter_map(|line| line.ok()) .filter(|line| set.is_match(line.as_str())) .for_each(|x| println!("{}", x)); Ok(()) }

文本模式替换

regex-badge lazy_static-badge cat-text-processing-badge

将所有出现的国际标准 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-badge cat-text-processing-badge

使用 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

std-badge cat-text-processing-badge

本实例中,创建一个自定义结构体 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 网页中提取所有链接reqwest-badge select-badgecat-net-badge
检查网页死链reqwest-badge select-badge url-badgecat-net-badge
从 MediaWiki 标记页面提取所有唯一性链接reqwest-badge regex-badgecat-net-badge

URL

实例名称Crates类别
解析 URL 字符串为 Url 类型url-badgecat-net-badge
通过移除路径段创建基本 URLurl-badgecat-net-badge
从基本 URL 创建新 URLsurl-badgecat-net-badge
提取 URL 源(scheme/ host/ port)url-badgecat-net-badge
从 URL 移除片段标识符和查询对url-badgecat-net-badge

媒体类型(MIME)

实例名称Crates类别
从字符串获取 MIME 类型mime-badgecat-encoding-badge
从文件名获取 MIME 类型mime-badgecat-encoding-badge
解析 HTTP 响应的 MIME 类型mime-badge reqwest-badgecat-net-badge cat-encoding-badge

客户端

请求处理

实例名称Crates类别
发出 HTTP GET 请求reqwest-badgecat-net-badge
为 REST 请求设置自定义消息标头和 URL 参数reqwest-badge hyper-badge url-badge cat-net-badge

Web API 调用

实例名称Crates类别
查询 GitHub APIreqwest-badge serde-badgecat-net-badge cat-encoding-badge
检查 API 资源是否存在reqwest-badgecat-net-badge
使用 GitHub API 创建和删除 Gistreqwest-badge serde-badgecat-net-badge cat-encoding-badge
使用 RESTful API 分页reqwest-badge serde-badgecat-net-badge cat-encoding-badge
处理速率受限 APIreqwest-badge hyper-badge cat-net-badge

下载

实例名称Crates类别
下载文件到临时目录reqwest-badge tempdir-badgecat-net-badge cat-filesystem-badge
使用 HTTP range 请求头进行部分下载reqwest-badgecat-net-badge
POST 文件到 paste-rsreqwest-badgecat-net-badge

提取链接

从 HTML 网页中提取所有链接

reqwest-badge select-badge cat-net-badge

使用 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(()) }

检查网页死链

reqwest-badge select-badge url-badge cat-net-badge

调用 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-badge regex-badge cat-net-badge

使用 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-badge cat-net-badge

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-badge cat-net-badge

基本 URL(base URL)包括协议和域名。但基本 URL(base URL)不包括目录、文件或查询字符串,这些项都可以从给定的 URL 中剥离出来。创建基本 URL(base URL)时,通过 PathSegmentsMut::clear 方法移除目录和文件路径,通过方法 Url::set_query 移除查询字符串。

use error_chain::error_chain; use url::Url; error_chain! { foreign_links { UrlParse(url::ParseError); } errors { CannotBeABase } } fn main() -> Result<()> { let full = "https://github.com/rust-lang/cargo?asdf"; let url = Url::parse(full)?; let base = base_url(url)?; assert_eq!(base.as_str(), "https://github.com/"); println!("The base of the URL is: {}", base); Ok(()) } fn base_url(mut url: Url) -> Result<Url> { match url.path_segments_mut() { Ok(mut path) => { path.clear(); } Err(_) => { return Err(Error::from_kind(ErrorKind::CannotBeABase)); } } url.set_query(None); Ok(url) }

从基本 URL 创建新 URLs

url-badge cat-net-badge

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-badge cat-net-badge

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 方法产生相同的结果。

use error_chain::error_chain; use url::{Url, Origin, Host}; error_chain! { foreign_links { UrlParse(url::ParseError); } } fn main() -> Result<()> { let s = "ftp://rust-lang.org/examples"; let url = Url::parse(s)?; let expected_scheme = "ftp".to_owned(); let expected_host = Host::Domain("rust-lang.org".to_owned()); let expected_port = 21; let expected = Origin::Tuple(expected_scheme, expected_host, expected_port); let origin = url.origin(); assert_eq!(origin, expected); println!("The origin is as expected!"); Ok(()) }

从 URL 移除片段标识符和查询对

url-badge cat-net-badge

解析 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-badge cat-encoding-badge

下面的实例展示如何使用 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-badge cat-encoding-badge

下面的实例展示如何使用 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-badge mime-badge cat-net-badge cat-encoding-badge

当从 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 请求reqwest-badgecat-net-badge
为 REST 请求设置自定义消息标头和 URL 参数reqwest-badge hyper-badge url-badge cat-net-badge

Web API 调用

实例名称Crates类别
查询 GitHub APIreqwest-badge serde-badgecat-net-badge cat-encoding-badge
检查 API 资源是否存在reqwest-badgecat-net-badge
使用 GitHub API 创建和删除 Gistreqwest-badge serde-badgecat-net-badge cat-encoding-badge
使用 RESTful API 分页reqwest-badge serde-badgecat-net-badge cat-encoding-badge
处理速率受限 APIreqwest-badge hyper-badge cat-net-badge

下载

实例名称Crates类别
下载文件到临时目录reqwest-badge tempdir-badgecat-net-badge cat-filesystem-badge
使用 HTTP range 请求头进行部分下载reqwest-badgecat-net-badge
POST 文件到 paste-rsreqwest-badgecat-net-badge

请求处理

发出 HTTP GET 请求

reqwest-badge cat-net-badge

解析提供的 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 参数

reqwest-badge hyper-badge url-badge cat-net-badge

本实例中为 HTTP GET 请求设置标准的和自定义的 HTTP 消息标头以及 URL 参数。使用 hyper::header! 宏创建 XPoweredBy 类型的自定义消息标头。

使用 Url::parse_with_params 构建复杂的 URL。使用 RequestBuilder::header 方法设置标准消息标头 header::UserAgentheader::Authorization,以及自定义类型 XPoweredBy,然后使用 RequestBuilder::send 发起请求。

请求的服务目标为 http://httpbin.org/headers,其响应结果是包含所有请求的消息标头的 JSON 字典,易于验证。

use error_chain::error_chain; use serde::Deserialize; use std::collections::HashMap; use url::Url; use reqwest::Client; use reqwest::header::{UserAgent, Authorization, Bearer}; header! { (XPoweredBy, "X-Powered-By") => [String] } #[derive(Deserialize, Debug)] pub struct HeadersEcho { pub headers: HashMap<String, String>, } error_chain! { foreign_links { Reqwest(reqwest::Error); UrlParse(url::ParseError); } } fn main() -> Result<()> { let url = Url::parse_with_params("http://httpbin.org/headers", &[("lang", "rust"), ("browser", "servo")])?; let mut response = Client::new() .get(url) .header(UserAgent::new("Rust-test")) .header(Authorization(Bearer { token: "DEadBEEfc001cAFeEDEcafBAd".to_owned() })) .header(XPoweredBy("Guybrush Threepwood".to_owned())) .send()?; let out: HeadersEcho = response.json()?; assert_eq!(out.headers["Authorization"], "Bearer DEadBEEfc001cAFeEDEcafBAd"); assert_eq!(out.headers["User-Agent"], "Rust-test"); assert_eq!(out.headers["X-Powered-By"], "Guybrush Threepwood"); assert_eq!(response.url().as_str(), "http://httpbin.org/headers?lang=rust&browser=servo"); println!("{:?}", out); Ok(()) }

Web API 调用

查询 GitHub API

reqwest-badge serde-badge cat-net-badge cat-encoding-badge

使用 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 资源是否存在

reqwest-badge cat-net-badge

使用消息标头 HEAD 请求((Client::head)查询 GitHub 用户端接口,然后检查响应代码以确定是否成功。这是一种无需接收 HTTP 响应消息主体,即可快速查询 rest 资源的方法。使用 ClientBuilder::timeout 方法配置的 reqwest::Client 结构体将确保请求不会超时。

由于 ClientBuilder::buildRequestBuilder::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

reqwest-badge serde-badge cat-net-badge cat-encoding-badge

使用 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 分页

reqwest-badge serde-badge cat-net-badge cat-encoding-badge

可以将分页的 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

reqwest-badge hyper-badge cat-net-badge

此实例使用 GitHub API - 速率限制展示如何处理远程服务器错误。本实例使用 hyper::header! 宏来解析响应头并检查 reqwest::StatusCode::Forbidden。如果响应超过速率限制,则将等待并重试。

use error_chain::error_chain; use std::time::{Duration, UNIX_EPOCH}; use std::thread; use reqwest::StatusCode; error_chain! { foreign_links { Io(std::io::Error); Time(std::time::SystemTimeError); Reqwest(reqwest::Error); } } header! { (XRateLimitLimit, "X-RateLimit-Limit") => [usize] } header! { (XRateLimitRemaining, "X-RateLimit-Remaining") => [usize] } header! { (XRateLimitReset, "X-RateLimit-Reset") => [u64] } fn main() -> Result<()> { loop { let url = "https://api.github.com/users/rust-lang-nursery "; let client = reqwest::Client::new(); let response = client.get(url).send()?; let rate_limit = response.headers().get::<XRateLimitLimit>().ok_or( "response doesn't include the expected X-RateLimit-Limit header", )?; let rate_remaining = response.headers().get::<XRateLimitRemaining>().ok_or( "response doesn't include the expected X-RateLimit-Remaining header", )?; let rate_reset_at = response.headers().get::<XRateLimitReset>().ok_or( "response doesn't include the expected X-RateLimit-Reset header", )?; let rate_reset_within = Duration::from_secs(**rate_reset_at) - UNIX_EPOCH.elapsed()?; if response.status() == StatusCode::Forbidden && **rate_remaining == 0 { println!("Sleeping for {} seconds.", rate_reset_within.as_secs()); thread::sleep(rate_reset_within); return main(); } else { println!( "Rate limit is currently {}/{}, the reset of this limit will be within {} seconds.", **rate_remaining, **rate_limit, rate_reset_within.as_secs(), ); break; } } Ok(()) }

下载

下载文件到临时目录

reqwest-badge tempdir-badge cat-net-badge cat-filesystem-badge

使用 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-badge cat-net-badge

使用 reqwest::blocking::Client::head 获取响应的消息主体的大小(即消息主体内容长度)。

然后,使用 reqwest::blocking::Client::get 下载 10240 字节的内容,同时打印进度消息。本实例使用同步的 reqwest 模块,消息范围标头指定响应的消息块大小和位置。

RFC7233 中定义了消息范围标头。

译者注: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-badge cat-net-badge

本实例使用 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(()) }