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 <[email protected]>")
        .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 <[email protected]>
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:[email protected]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:[email protected]/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:[email protected]/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! <[email protected]>"]

          [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! <[email protected]>"]

          [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! <[email protected]>"]);
    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 protected]"), Some(r"I❤email"));
    assert_eq!(
        extract_login(r"[email protected]"),
        Some(r"sdf+sdsfsd.as.sdsd")
    );
    assert_eq!(extract_login(r"[email protected]@[email protected]"), None);
    assert_eq!(extract_login(r"Not an [email protected]"), 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(())
}