Rust 烹饪书
这个Rust 烹饪书是一套简单的例子,它演示了如何使用 Rust 生态系统的箱子,作为完成常见编程任务的良好实践。
了解 Rust 烹饪书 更多,包括如何阅读这本书的提示、如何使用示例,以及约定好的注释风格。
贡献
这个项目的目的是,让新的 Rust 程序员能够容易做出贡献,帮个小忙,并且是一个与 Rust 社区接触的简单方法。本库十分欢迎帮助。详情见CONTRIBUTING.md.
算法
食谱 | 箱子 | 分类 |
---|---|---|
生成,随机数 | ||
生成,范围内的随机数 | ||
生成,具有给定分布的随机数 | ||
生成,自定义类型的随机值 | ||
从一组字母字符,创建随机密码 | ||
从一组用户定义的字符,创建随机密码 | ||
排序一个,整数的 vector | ||
排序一个,浮点的 vector | ||
排序一个,结构的 vector |
命令行
食谱 | 箱子 | 分类 |
---|---|---|
解析命令行参数 | ||
ANSI 终端 |
压缩
食谱 | 箱子 | 分类 |
---|---|---|
解压缩 一个 tarball | ||
将一个目录压缩为 tarball | ||
从路径中删除前缀时,解压缩一个 tarball |
并发性
食谱 | 箱子 | 类别 |
---|---|---|
生成一个短命线程 | ||
保持全局可变状态 | ||
并发计算所有 *.iso 文件的 SHA1 和 | ||
将绘制分形工作,分派到线程池 | ||
并行,改变数组的元素 | ||
如果集合的任何或所有元素,与给定物匹配,则并行测试 | ||
并行,使用给定物搜索项 | ||
并行,排序 vector | ||
并行,缩小地图 | ||
并行,生成 JPG 缩略图 |
密码学
食谱 | 箱子 | 类别 |
---|---|---|
计算文件的 SHA-256 码 | ||
使用 HMAC 码,签名并验证消息 | ||
用 PBKDF2 对密码,进行 加盐(Salt) 和 哈希 操作 |
数据结构
食谱 | 箱子 | 类别 |
---|---|---|
定义表示为位字段的类型,并操作 |
数据库
食谱 | 箱子 | 类别 |
---|---|---|
创建 sqlite 数据库 | ||
插入和查询数据 | ||
在 Postgres 数据库中,创建表 | [![postgres-badge]][postgres] | |
插入和查询数据 | [![postgres-badge]][postgres] | |
综合数据 | [![postgres-badge]][postgres] |
日期和时间
食谱 | 箱子 | 类别 |
---|---|---|
测量已用时间 | ||
执行,检查日期和时间的计算 | ||
将本地时间,转换为其他时区 | ||
检查日期和时间 | ||
将日期转换为 Unix 时间戳,或相反 | ||
显示格式化的日期和时间 | ||
将字符串解析为 DateTime 结构 |
开发工具
调试
食谱 | 箱子 | 类别 |
---|---|---|
将调试消息,记录到控制台 | ||
将错误消息,记录到控制台 | ||
记录到 stdout ,而不是 stderr | ||
使用自定义记录器,记录消息 | ||
记录到 Unix 系统日志 | ||
启用每个模块的日志级别 | ||
使用自定义环境变量,设置日志记录 | ||
在日志消息中,包含时间戳 | ||
将消息记录,到自定义位置 |
版本控制
食谱 | 箱子 | 类别 |
---|---|---|
解析,并增加版本字符串 | ||
分析,复杂版本字符串 | ||
检查给定版本,是否为预发布版本 | ||
查找,满足给定范围的最新版本 | ||
检查外部命令版本的兼容性 |
构建时
食谱 | 箱子 | 类别 |
---|---|---|
静态编译,并链接到捆绑的 C 库 | ||
编译,并链接到捆绑的 C++库 | ||
自定义设置时,编译 C 库 |
编码
文件系统
食谱 | 箱子 | 类别 |
---|---|---|
从文件中,读取字符串行 | ||
避免写入和读取,同一文件 | ||
随机使用内存映射,访问文件 | ||
过去 24 小时内,修改过的文件名 | ||
查找给定路径的循环 | ||
递归查找,重复的文件名 | ||
递归查找,具有给定断言的所有文件 | ||
跳过点(隐藏)文件,遍历目录 | ||
在给定深度(目录),递归计算文件大小 | ||
递归查找,所有 PNG 文件 | ||
查找具有给定模式的所有文件,忽略文件名大小写 |
硬件支持
食谱 | 箱子 | 类别 |
---|---|---|
检查逻辑 CPU 的核数 |
内存管理
食谱 | 箱子 | 类别 |
---|---|---|
声明,延迟计算的常量 |
网络
食谱 | 箱子 | 类别 |
---|---|---|
侦听,未使用的端口 TCP/IP |
操作系统
食谱 | 箱子 | 类别 |
---|---|---|
运行外部命令,并处理 stdout | ||
运行传递到 stdin 的外部命令,并检查错误代码 | ||
运行管道的外部命令 | ||
将子进程的 stdout 和 stderr ,重定向到同一文件 | ||
连续处理,子进程的输出 |
科学类
数学
食谱 | 箱子 | 类别 |
---|---|---|
vector 和 | ||
vector 范数 | ||
矩阵相加 | ||
矩阵乘法 | ||
用 vector 和矩阵,相乘一个标量 | ||
反转矩阵 | [![nalgebra-badge]][nalgebra] | |
计算三角形的边长 | ||
验证 tan 等于 sin 除以 cos | ||
地球两点之间的距离 | ||
创建复数 | ||
复数相加 | ||
复数的数学函数 | ||
集中趋势度量 | ||
计算标准偏差 | ||
大整数 |
文本处理
食谱 | 箱子 | 类别 |
---|---|---|
收集 Unicode 字形 | [![unicode-segmentation-badge]][unicode-segmentation] | |
从电子邮件地址,提取登录信息并验证 | ||
从文本中,提取独一的# 标签列表 | ||
从文本中,提取电话号码 | ||
通过匹配多个正则表达式,筛选日志文件 | ||
将一个文本模式的所有出现项,替换为另一个模式。 | ||
为一个自定义struct ,实现FromStr trait |
网页编程
刮擦网页
食谱 | 箱子 | 类别 |
---|---|---|
从网页 HTML 中,提取所有链接 | ||
检查网页,是否有断开的链接 | ||
从 Mediawiki markup 中,提取所有独一链接 |
统一资源位置(URL)
食谱 | 箱子 | 类别 |
---|---|---|
将字符串的一个 URL,解析为Url 类型 | ||
通过移除路径段,创建一个 base URL | ||
从 base URL ,创建新的 URL | ||
提取 URL 源(方案名/主机/端口) | ||
从 URL 中,删除片段标识符和查询对 |
媒体类型(MIME)
食谱 | 箱子 | 类别 |
---|---|---|
从字符串,获取 MIME 类型 | ||
从文件名,获取 MIME 类型 | ||
解析 HTTP 响应的 MIME 类型 |
客户端
食谱 | 箱子 | 类别 |
---|---|---|
发出 HTTP GET 请求 | ||
查询 GitHub API | ||
检查 API 资源,是否存在 | ||
使用 GitHub API ,创建和删除 Gist | ||
使用一个(具备)分页的 RESTful API | ||
将文件下载到临时目录 | ||
使用 HTTP range 标头,进行部分下载 | ||
POST 文件,到 paste.rs |
关于“Rust 烹饪”
目录
这本书的目标受众
本手册适用于新的 Rust 程序员,因此他们可以快速了解 Rust crate(箱子) 生态系统的能力。它也适用于经验丰富的 Rust 程序员,他们应该在食谱中,找到如何完成常见任务的简单提示。
如何阅读本书
烹饪书的索引页包含完整的食谱列表,分为几个部分:“基础”,“编码”,“并发”等。这些部分本身或多或少地按顺序排列,后面的部分,更先进,偶尔是在前面部分的概念之上构建。
在索引中,每个部分都包含一个食谱列表。食谱是要完成任务的简单陈述,例如“生成范围内的随机数”;并且每个食谱都标有徽章,表明哪个箱子是他们所使用的,像,以及那些箱子在crates.io上,属于哪些类别。像。
新的 Rust 程序员,应该从第一部分开始,按顺序到最后一部分,这样阅读起来会很舒服,这样做可以让人们对 crate 生态系统,有一个很好的大菊观。单击,索引中的章节标题,或单击,侧栏以导航到某章节页面。
如果,您只是在寻找简单任务的解决方案,那么今时今日的烹饪书,还较难导航。查找特定食谱的最简单方法是扫描索引,查找感兴趣的箱子和类别。从那里,单击食谱名称进行查看。这导航方面在未来会改善的。
如何使用食谱
食谱旨在让您即时访问到,能工作的代码,并全面解释其正在执行的操作,并指导您获取更多信息。
烹饪书中的所有食谱都是完整的,自包含(自给自足)的程序,因此可以将它们直接复制到,您自己的项目中进行实验。为此,请按照以下说明,进行操作。
考虑这个例子:“生成一个范围内的随机数”:
extern crate rand; 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
现在,我们还需要添加必要的箱子到Cargo.toml,如箱子徽章所示,在这种情况下只是“rand”。为此,我们将使用cargo add
命令,这是由cargo-edit
箱子提供的(自定义)命令,我们需要先安装:
cargo install cargo-edit
cargo add rand
现在,你可以把src/main.rs
,替换成,用例中的完整内容并运行:
cargo run
示例附带的箱子徽章,链接到箱子的完整docs.rs文档(一个专放Rust项目文档的官方托管网站),并且通常,在你决定哪个箱子套件后,您应该阅读相关文档。
关于错误处理的说明
如果做得正确,Rust 中的错误处理是健壮的,但对今时今日的 Rust 来说,就需要相当多的模版。正是这个原因,你经常看到 Rust 的例子,充满了unwrap
调用,而不是正确的错误处理。
由于这些食谱,具有原样复用的目标,并鼓励最佳实践,因此当涉及Result
类型,它们就会正确设置错误处理。
我们使用的基本模式是,具备一个fn run() -> Result
,让它像“真正的”main 函数行动。我们用的是error-chain箱子,使?
在run
里面工作。
错误处理的结构,通常如下:
#[macro_use] extern crate error_chain; use std::net::IpAddr; use std::str; error_chain! { foreign_links { Utf8(std::str::Utf8Error); AddrParse(std::net::AddrParseError); } } fn run() -> 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(()) } quick_main!(run);
这是使用error_chain!
宏,自定义一个Error
和Result
类型,以及来自两个标准库的错误类型的自动转换。自动转换使?
操作符工作。该quick_main!
宏生成,实际main
,并在发生错误时,打印出错误。
为了便于阅读,错误处理模版,默认隐藏,如下所示。要阅读完整内容,请单击“展开”()按钮,它位于代码段的右上角。
# #[macro_use] # extern crate error_chain; extern crate url; use url::{Url, Position}; # # error_chain! { # foreign_links { # UrlParse(url::ParseError); # } # } fn run() -> Result<()> { let parsed = Url::parse("https://httpbin.org/cookies/set?k2=v2&k1=v1")?; let cleaned: &str = &parsed[..Position::AfterPath]; println!("cleaned: {}", cleaned); Ok(()) } # # quick_main!(run);
有关 Rust 中错误处理的更多背景知识,请阅读这本 Rust 书和这篇博文。
关于箱子表示的说明
这本烹饪书,最终的目标,是提供对 Rust crate 生态系统的广泛认知,但是,今天我们在它的引导下,进行演示时,范围还很有限。希望从小范围开始,缓慢扩展(认知),将有助于烹饪书,更快地成为高质量的资源,常话说:不要急,最重要快!并在增长时,保持一致的质量水平。
目前,烹饪书专注于标准库,以及“核心”或“基础”,箱子 - 都是构成最常见的编程任务的那些箱子,其余的,就是生态系统的部分构建。
这本烹饪书与Rust Libz Blitz密切相关,一个识别和提高此类箱子质量的项目,因此该项目在很大程度上,延迟箱子入选的问题。该过程的一部分,作为已经评估的任何箱子,都在烹饪书的(书写)范围内,而正在等待评估的箱子也是如此。
算法
食谱 | 箱子 | 分类 |
---|---|---|
生成,随机数 | ||
生成,范围内的随机数 | ||
生成,具有给定分布的随机数 | ||
生成,自定义类型的随机值 | ||
从一组字母字符,创建随机密码 | ||
从一组用户定义的字符,创建随机密码 | ||
排序一个,整数的 vector | ||
排序一个,浮点的 vector | ||
排序一个,结构的 vector |
生成随机值
生成随机数
通过rand::thread_rng
,在随机数生成器rand::Rng
的帮助下,生成随机数。每个线程都有一个初始化的生成器。整数在该类型的范围(最大值 ~ 最小值)内,均匀分布,还有,浮点数是从 0 到 1,但不包括 1 的 均匀分布。
extern crate rand; use rand::Rng; fn main() { let mut rng = rand::thread_rng(); let n1: u8 = rng.gen(); let n2: u16 = rng.gen(); println!("Random u8: {}", n1); println!("Random u16: {}", n2); println!("Random u32: {}", rng.gen::<u32>()); println!("Random i32: {}", rng.gen::<i32>()); println!("Random float: {}", rng.gen::<f64>()); }
生成范围内的随机数
用Rng::gen_range
,在半开放[0, 10)
范围(不包括10
)生成随机值。
extern crate rand; 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
可以用来获得均匀分布的值。下面的代码是相同作用,但是当在相同范围内,重复生成数字时可能更快。
extern crate rand; 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; } } }
生成给定分布的随机数
默认情况下,随机数有均匀(Uniform) 分布。要使用其他(概率/类型的)分布生成数字,您需要实例化一个分布(distribution),然后用分布下的Distribution::sample
方法,在随机数生成器rand::Rng
的帮助下,进行采样。
关于可用分布的文档,在此。下面是,一个使用Normal
分布的的例子。
extern crate rand; use rand::distributions::{Normal, Distribution}; fn main() { let mut rng = rand::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) }
生成自定义类型的随机值
随机生成一个元组(i32, bool, f64)
,和用户定义类型的变量Point
。在 Point
类型之上,对Standard
实现Distribution
trait,为了(Point
)允许随机生成(gen
)。
这里,用得是,具象化泛型类型。
extern crate rand; use rand::Rng; use rand::distributions::{Distribution, Standard}; #[derive(Debug)] struct Point { x: i32, y: i32, } // 把 泛型 具象化为 Point, (定义实现) 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(); // 主要是 : Point,类型标签,让编译器知道 (调用上面的实现定义) println!("Random tuple: {:?}", rand_tuple); println!("Random Point: {:?}", rand_point); }
从一组字母+数字的字符,创建随机密码
随机生成,给定长度的 ASCII 字符串,字符范围是A-Z, a-z, 0-9
,运用Alphanumeric
(字母+数字)样品。
extern crate rand; use rand::{thread_rng, Rng}; use rand::distributions::Alphanumeric; fn main() { let rand_string: String = thread_rng() .sample_iter(&Alphanumeric) .take(30) .collect(); println!("{}", rand_string); }
从一组用户定义的字符,创建随机密码
使用用户自定义的字节字符串,随机生成给定长度的 ASCII 字符串,运用gen_range
。
extern crate rand; 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()); // 这是安全的,因为 `idx` 会在 `CHARSET` 的范围内。 char::from(unsafe { *CHARSET.get_unchecked(idx) }) // 来自用户的所有输入,最好都定义为不安全的。 }) .collect(); println!("{:?}", password); }
排序 vector
排序整数 vector
此示例使用vec::sort
对整数 vector 进行排序。替代方案是使用vec::sort_unstable
,它可以更快,但不保留相等元素的顺序。
fn main() { let mut vec = vec![1, 5, 10, 2, 15]; vec.sort(); assert_eq!(vec, vec![1, 2, 5, 10, 15]); }
排序浮点数 vector
可以使用vec::sort_by
和PartialOrd::partial_cmp
,对 f32
或 f64
的 vector 进行排序。
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
对 Person 结构的 Vector 进行排序,通过属性name
和age
的自然顺序(按名称和年龄)。为了使 Person 可排序,你需要四个 traitEq
,PartialEq
,Ord
和PartialOrd
。可以简单地derive
出这些特征。您还可以使用一个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), ]; // 自然顺序,排序 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), ]); // 用 年龄 排序 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), ]); }
命令行
食谱 | 箱子 | 分类 |
---|---|---|
解析命令行参数 | ||
ANSI 终端 |
拍手(clap)基本
解析命令行参数
通过使用clap
的构建样式,该应用程序描述了其命令行界面的结构。该文档提供了,另外两种可能的方法,去实例化应用程序。
在构建样式中,with_name
是唯一标识符,而value_of
则用于检索传递的值。该short
和long
选项控制用户将要键入的标志; short
标志-f
和long
标志--file
是同个标志。
extern crate clap; use clap::{Arg, App}; fn main() { let matches = App::new("My Test Program") .version("0.1.0") .author("Hackerman Jones <hckrmnjones@hack.gov>") .about("Teaches argument parsing") .arg(Arg::with_name("file") .short("f") .long("file") .takes_value(true) .help("A cool file")) .arg(Arg::with_name("num") .short("n") .long("number") .takes_value(true) .help("Five less than your favorite number")) .get_matches(); let myfile = matches.value_of("file").unwrap_or("input.txt"); println!("The file passed is: {}", myfile); let num_str = matches.value_of("num"); match num_str { None => println!("No idea what your favorite number is."), Some(s) => { match s.parse::<i32>() { Ok(n) => println!("Your favorite number must be {}.", n + 5), Err(_) => println!("That's not a number! {}", s), } } } }
help
信息,由clap
生成。该示例应用程序的用法,如下所示。
My Test Program 0.1.0
Hackerman Jones <hckrmnjones@hack.gov>
Teaches argument parsing
USAGE:
testing [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-f, --file <file> A cool file
-n, --number <num> Five less than your favorite number
我们可以通过运行如下命令,来测试应用程序。
$ cargo run -- -f myfile.txt -n 251
输出是:
The file passed is: myfile.txt
Your favorite number must be 256.
ANSI 终端
ANSI终端
这个程序描述了[ansi_term
箱子]的用法,以及它是如何用于控制ANSI终端上的颜色和格式,例如,蓝色粗体文本或带黄色下划线的文本。
[ansi_term
]有两个主要的数据结构:ANSIString
和Style
。一个Style
保存风格信息:颜色,文本应该是粗体,还是闪烁,或者其他什么的。还有Colour
变体代表简单的前景色样式。一个ANSIString
是与一个Style
配对的字符串。
注意:英国英语,要使用Colour代替Color,不要混淆
将彩色文本打印到终端
extern crate ansi_term; 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()
创建结构,和要链接的属性。
extern crate ansi_term; use ansi_term::Style; fn main() { println!("{} and this is not", Style::new().bold().paint("This is Bold")); }
终端中的粗体和彩色文本
Colour
实现了许多与Style
类似的函数,并且可以链式方法。
extern crate ansi_term; 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")); }
压缩
食谱 | 箱子 | 分类 |
---|---|---|
解压缩 一个 tarball | ||
将一个目录压缩为 tarball | ||
从路径中删除前缀时,解压缩一个 tarball |
使用 Tarballs
解压缩 一个 tarball
解压缩(GzDecoder
)和提取(Archive::unpack
)当前工作目录中的压缩包archive.tar.gz
的所有文件,并放在同一位置。
extern crate flate2; extern crate tar; 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(()) }
将目录压缩为一个 tarball
把/var/log
目录压缩,为archive.tar.gz
。
创建一个File
,包进GzEncoder
,在包入tar::Builder
。
- 用
Builder::append_dir_all
递归添加/var/log
目录中的内容,归档到backup/logs
路径下。 GzEncoder
负责在数据写入archive.tar.gz
之前,透明地压缩数据。
extern crate tar; extern crate flate2; 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(()) }
从路径中删除前缀时,解压缩一个 tarball
迭代Archive::entries
。使用Path::strip_prefix
删除指定的路径前缀(bundle/logs
)。最后,通过Entry::unpack
,提取出tar::Entry
- 压缩中的项。
# #[macro_use] # extern crate error_chain; extern crate flate2; extern crate tar; 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(()) }
并发性
食谱 | 箱子 | 类别 |
---|---|---|
生成一个短命线程 | ||
保持全局可变状态 | ||
并发计算所有 *.iso 文件的 SHA1 和 | ||
将绘制分形工作,分派到线程池 | ||
并行,改变数组的元素 | ||
如果集合的任何或所有元素,与给定物匹配,则并行测试 | ||
并行,使用给定物搜索项 | ||
并行,排序 vector | ||
并行,缩小地图 | ||
并行,生成 JPG 缩略图 |
线程
生成一个短命的线程
该示例使用了crossbeam箱子,为并发和并行编程,提供数据结构和函数。Scope::spawn
生成一个新的作用域线程,保证crossbeam::scope
函数的闭包(参数)返回之前终止,意味着,您可以从调用的函数中,引用数据。
此示例将数组拆分为一半,并在单独的线程中,执行工作。
extern crate crossbeam; 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 min_l = thread_l.join().unwrap()?; let min_r = thread_r.join().unwrap()?; Some(min_l.max(min_r)) }).unwrap() }
保持全球可变状态
使用lazy_static声明全局状态。lazy_static创建一个全局可用static ref
,这需要一个Mutex
,来决定可变许可(也参见RwLock
)。受Mutex
包裹,能确保多个线程,不能同时访问(FRUIT
)状态,从而防止竞争条件。必须获取一个MutexGuard
,才能读取或改变存储在Mutex
中的值。
# #[macro_use] # extern crate error_chain; #[macro_use] extern crate 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 run() -> 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(()) } # # quick_main!(run);
并发计算,所有 iso 文件的 SHA1 和
本示例:对当前目录中,具有 ISO 扩展名的每个文件,合计它们的 SHA1。线程池生成的线程数,等于系统(CPU)核心数,这个能通过num_cpus::get
获取。 Walkdir::new
迭代当前目录,并调用execute
执行读取和计算 SHA1 哈希的操作。
extern crate walkdir; extern crate ring; extern crate num_cpus; extern crate threadpool; 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, SHA1}; # // 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(&SHA1); 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(()) }
将绘制分形工作,分派到线程池
本示例:绘制[朱利亚集合]的一个分形,生成一个图像,会用到分布式计算的线程池。
通过ImageBuffer::new
,为给定宽度和高度的输出图像,分配内存。Rgb::from_channels
计算 RGB 像素值。创建ThreadPool
线程数,等于num_cpus::get
核心数。ThreadPool::execute
收到的每个像素,都作为一个单独的工作。
mpsc::channel
收到工作,还有Receiver::recv
会检索它们。ImageBuffer::put_pixel
使用数据,设置像素颜色。ImageBuffer::save
就将图像写入output.png
。
# #[macro_use] # extern crate error_chain; extern crate threadpool; extern crate num; extern crate num_cpus; extern crate image; 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); # } # } # # // Function converting intensity values to RGB # // Based on 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) # } # # // Maps Julia set distance estimation to intensity values # 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 # } # # // Normalizes color intensity values within RGB range # fn normalize(color: f32, factor: f32) -> u8 { # ((color * factor).powf(0.8) * 255.) as u8 # } fn run() -> 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(()) } # # quick_main!(run);
并行任务
并行,变换数组的元素
该示例使用了rayon
箱子,这是 Rust 的数据并行库。rayon
提供了par_iter_mut
方法,给任何的并行可迭代数据类型使用。这是一个类似迭代器的链,(潜在地)并行执行。
extern crate rayon; use rayon::prelude::*; fn main() { let mut arr = [0, 7, 9, 11]; arr.par_iter_mut().for_each(|p| *p -= 1); println!("{:?}", arr); }
并行测试,集合的任何或所有元素是否与给定断言匹配
这个例子演示了如何使用rayon::any
和rayon::all
方法,是与std::any
和std::all
相对应的并行方法。rayon::any
并行检查迭代器的任何元素,是否与断言匹配,并在找到一个元素后立即返回。rayon::all
并行检查迭代器的所有元素,是否与断言匹配,并在找到非匹配元素后,立即返回。
extern crate rayon; use rayon::prelude::*; fn main() { let mut vec = vec![2, 4, 6, 8]; assert!(!vec.par_iter().any(|n| (*n % 2) != 0)); assert!(vec.par_iter().all(|n| (*n % 2) == 0)); assert!(!vec.par_iter().any(|n| *n > 8 )); assert!(vec.par_iter().all(|n| *n <= 8 )); vec.push(9); assert!(vec.par_iter().any(|n| (*n % 2) != 0)); assert!(!vec.par_iter().all(|n| (*n % 2) == 0)); assert!(vec.par_iter().any(|n| *n > 8 )); assert!(!vec.par_iter().all(|n| *n <= 8 )); }
使用给定断言,并行搜索项目
这个例子使用rayon::find_any
和par_iter
获得一个,通过并行搜索,满足给定闭包中断言的元素 vector。
如果,有多个元素满足rayon::find_any
闭包参数中,定义的断言,rayon
返回找到的第一个,但不一定是(顺序上的)第一个。
另请注意,闭包的参数是对一个引用的一个引用(&&x
)。请查阅std::find
的讨论,了解更多细节。
extern crate rayon; use rayon::prelude::*; fn main() { let v = vec![6, 2, 1, 9, 3, 8, 11]; let f1 = v.par_iter().find_any(|&&x| x == 9); let f2 = v.par_iter().find_any(|&&x| x % 2 == 0 && x > 6); let f3 = v.par_iter().find_any(|&&x| x > 8); assert_eq!(f1, Some(&9)); assert_eq!(f2, Some(&8)); assert!(f3 > Some(&8)); }
并行,排序 vector
此示例:并行排序字符串 vector。
分配一个空字符串的 vector。par_iter_mut().for_each
会并发填充随机值。虽然存在多种选择,可以对可枚举数据类型进行排序,par_sort_unstable
通常比稳定排序算法快。
extern crate rand; extern crate rayon; use rand::{Rng, thread_rng}; use rand::distributions::Alphanumeric; use rayon::prelude::*; fn main() { let mut vec = vec![String::new(); 100_000]; vec.par_iter_mut().for_each(|p| { let mut rng = thread_rng(); *p = (0..5).map(|_| rng.sample(&Alphanumeric)).collect() }); vec.par_sort_unstable(); }
Map-reduce 并行
这个例子使用rayon::filter
,rayon::map
,和rayon::reduce
,计算Person
对象的平均年龄,哪个年龄超过 30 。
rayon::filter
返回满足给定断言的集合,其中的元素。rayon::map
对每个元素执行一个操作,创建一个新的迭代,然后rayon::reduce
:执行带有一个当前元素,和前一个(执行结果)的操作。还显示了使用rayon::sum
,与本例中的 reduce 操作,具有相同的结果。
extern crate rayon; use rayon::prelude::*; struct Person { age: u32, } fn main() { let v: Vec<Person> = vec![ Person { age: 23 }, Person { age: 19 }, Person { age: 42 }, Person { age: 17 }, Person { age: 17 }, Person { age: 31 }, Person { age: 30 }, ]; let num_over_30 = v.par_iter().filter(|&x| x.age > 30).count() as f32; let sum_over_30 = v.par_iter() .map(|x| x.age) .filter(|&x| x > 30) .reduce(|| 0, |x, y| x + y); let alt_sum_30: u32 = v.par_iter() .map(|x| x.age) .filter(|&x| x > 30) .sum(); let avg_over_30 = sum_over_30 as f32/num_over_30; let alt_avg_over_30 = alt_sum_30 as f32/ num_over_30; assert!((avg_over_30 - alt_avg_over_30).abs() < std::f32::EPSILON); println!("The average age of people older than 30 is {}", avg_over_30); }
并行,生成 jpg 缩略图
此示例:帮当前目录中的所有.jpg
文件生成缩略图,然后将其保存在名为thumbnails
的新文件夹中。
glob::glob_with
在当前目录中,查找 jpeg 文件。rayon
使用par_iter
,并发调整图像大小,每次都调用DynamicImage::resize
。
# #[macro_use] # extern crate error_chain; extern crate glob; extern crate image; extern crate rayon; 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 run() -> Result<()> { let options: MatchOptions = Default::default(); let files: Vec<_> = glob_with("*.jpg", &options)? .filter_map(|x| x.ok()) .collect(); if files.len() == 0 { 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)?) } # # quick_main!(run);
密码学
食谱 | 箱子 | 类别 |
---|---|---|
计算文件的 SHA-256 码 | ||
使用 HMAC 码,签名并验证消息 | ||
用 PBKDF2 对密码,进行 加盐(Salt) 和 哈希 操作 |
哈希
计算文件的 SHA-256 码
将一些数据写入文件,然后获得文件的内容,再用使用digest::Context
,digest::Digest
计算 SHA-256 码。
# #[macro_use] # extern crate error_chain; extern crate data_encoding; extern crate ring; 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 run() -> 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(()) } # # quick_main!(run);
与 HMAC 码,签署并验证消息
使用ring::hmac
创建一个hmac::Signature
,然后验证签名是否正确。
extern crate ring; use ring::{digest, 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::SigningKey::new(&digest::SHA256, &key_value); let message = "Legitimate and important message."; let signature = hmac::sign(&key, message.as_bytes()); hmac::verify_with_own_key(&key, message.as_bytes(), signature.as_ref())?; Ok(()) }
加密
用 PBKDF2 对密码,进行 加盐 和 哈希 操作
ring::pbkdf2
的用法是,使用 PBKDF2 密钥派生函数pbkdf2::derive
,哈希 腌制的密码。 用pbkdf2::verify
验证哈希是否正确。 盐是用SecureRandom::fill
生成的,它用安全生成的随机数,填充 salt 字节数组。
这个在哈希中加入字符串的方式称为“加盐”。其作用是让加盐后的哈希结果和没有加盐的结果不相同,在不同的应用情景中,这个处理可以增加额外的安全性。
extern crate ring; extern crate data_encoding; use data_encoding::HEXUPPER; use ring::error::Unspecified; use ring::rand::SecureRandom; use ring::{digest, pbkdf2, rand}; fn main() -> Result<(), Unspecified> { const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN; const N_ITER: u32 = 100_000; 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( &digest::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( &digest::SHA512, N_ITER, &salt, password.as_bytes(), &pbkdf2_hash, ); let wrong_password = "Definitely not the correct password"; let should_fail = pbkdf2::verify( &digest::SHA512, N_ITER, &salt, wrong_password.as_bytes(), &pbkdf2_hash, ); assert!(should_succeed.is_ok()); assert!(!should_fail.is_ok()); Ok(()) }
数据结构
食谱 | 箱子 | 类别 |
---|---|---|
定义表示为位字段的类型,并操作 |
自定义
定义表示为位字段的类型,并操作
创建,类型安全位字段的类型MyFlags
,这是借助于bitflags!
宏,和实现元素的clear
操作,还有Display
。随后,显示基本的位操作和格式设置。
#[macro_use] extern crate 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 数据库 | ||
插入和查询数据 | ||
在 Postgres 数据库中,创建表 | [![postgres-badge]][postgres] | |
插入和查询数据 | [![postgres-badge]][postgres] | |
综合数据 | [![postgres-badge]][postgres] |
数据库
创建 sqlite 数据库
使用rusqlite
箱子,打开 sqlite 数据库。见箱子文档中 Windows 的编译。
Connection::open
:如果数据库不存在,将创建该数据库。
extern crate rusqlite; use rusqlite::{Connection, Result}; use rusqlite::NO_PARAMS; fn main() -> Result<()> { let conn = Connection::open("cats.db")?; conn.execute( "create table if not exists cat_colors ( id integer primary key, name text not null unique )", NO_PARAMS, )?; conn.execute( "create table if not exists cats ( id integer primary key, name text not null, color_id integer not null references cat_colors(id) )", NO_PARAMS, )?; Ok(()) }
插入并查询数据
Connection::open
将打开在早期食谱中,创建的数据库cats
。此食谱将数据插入到cat_colors
,还有和cats
表用到Connection
的execute
方法。 首先,将数据插入cat_colors
表。插入颜色(color)记录后,Connection
的last_insert_rowid
方法,能获取最后插入(color)的id
。这个id
能用来,把数据插入到cats
表。然后,使用prepare
方法准备查询,它会返回statement
结构。然后,再使用statement
的query_map
方法(进行查询).
extern crate rusqlite; use rusqlite::{Connection, Result}; use rusqlite::NO_PARAMS; use std::collections::HashMap; #[derive(Debug)] struct Cat { name: String, color: String } fn main() -> Result<()> { let conn = Connection::open("cats.db")?; let mut cat_colors = HashMap::new(); cat_colors.insert(String::from("Blue"), vec!["Tigger", "Sammy"]); cat_colors.insert(String::from("Black"), vec!["Oreo", "Biscuit"]); for (color, catnames) in &cat_colors{ conn.execute( "INSERT INTO cat_colors (name) values (?1)", &[&color.to_string()], )?; let last_id : String = conn.last_insert_rowid().to_string(); for cat in catnames{ conn.execute( "INSERT INTO cats (name, color_id) values (?1, ?2)", &[&cat.to_string(), &last_id], )?; } } let mut stmt = conn.prepare("SELECT c.name, cc.name from cats c INNER JOIN cat_colors cc ON cc.id = c.color_id;")?; let cats = stmt .query_map(NO_PARAMS, |row| Ok( Cat { name: row.get(0)?, color: row.get(1)?, } ) )?; for cat in cats { println!("Found cat {:?}", cat); } Ok(()) }
使用事务
Connection::open
将打开cats.db
—— 来自之前食谱的数据库。
用Connection::transaction
开始一个事务(transaction)。事务将回滚,除非明确使用Transaction::commit
提交。
一次事务,就是一系列对数据库的操作,且明确
commit
后才会执行。
在下面的示例中,将颜色添加表,该表对颜色名称要唯一,进行约束。当尝试插入重复的颜色时,事务将回滚。
extern crate rusqlite; use rusqlite::{Connection, Result, NO_PARAMS}; fn main() -> Result<()> { let mut conn = Connection::open("cats.db")?; successful_tx(&mut conn)?; let res = rolled_back_tx(&mut conn); assert!(res.is_err()); Ok(()) } fn successful_tx(conn: &mut Connection) -> Result<()> { let tx = conn.transaction()?; tx.execute("delete from cat_colors", NO_PARAMS)?; tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?; tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?; tx.commit() } fn rolled_back_tx(conn: &mut Connection) -> Result<()> { let tx = conn.transaction()?; tx.execute("delete from cat_colors", NO_PARAMS)?; tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?; tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?; tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?; tx.commit() }
与 Postgres 合作
在 Postgres 数据库中,创建表
使用postgres
在 Postgres 数据库中,创建表。
Connection::connect
帮助连接到现有数据库。该食谱的Connection::connect
使用一个 URL 字符串格式。 它假定一个名为library
,用户名是postgres
,密码是postgres
.
extern crate postgres; use postgres::{Connection, TlsMode, Error}; fn main() -> Result<(), Error> { let conn = Connection::connect("postgresql://postgres:postgres@localhost/library", TlsMode::None)?; conn.execute("CREATE TABLE IF NOT EXISTS author ( id SERIAL PRIMARY KEY, name VARCHAR NOT NULL, country VARCHAR NOT NULL )", &[])?; conn.execute("CREATE TABLE IF NOT EXISTS book ( id SERIAL PRIMARY KEY, title VARCHAR NOT NULL, author_id INTEGER NOT NULL REFERENCES author )", &[])?; Ok(()) }
插入和查询数据
该食谱,用Connection
的execute
方法,将数据插入author
表。 然后,用Connection
的query
方法,显示author
表。
extern crate postgres; use postgres::{Connection, TlsMode, Error}; use std::collections::HashMap; struct Author { id: i32, name: String, country: String } fn main() -> Result<(), Error> { let conn = Connection::connect("postgresql://postgres:postgres@localhost/library", TlsMode::None)?; 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() }; conn.execute("INSERT INTO author (name, country) VALUES ($1, $2)", &[&author.name, &author.country])?; } for row in &conn.query("SELECT id, name, country FROM author", &[])? { let author = Author { id: row.get(0), name: row.get(1), country: row.get(2), }; println!("Author {} is from {}", author.name, author.country); } Ok(()) }
汇总数据
该食谱,列出了数据库中,现代艺术博物馆的前 7999 名艺术家的国籍,按降序排列。
extern crate postgres; use postgres::{Connection, Error, TlsMode}; struct Nation { nationality: String, count: i64, } fn main() -> Result<(), Error> { let conn = Connection::connect( "postgresql://postgres:postgres@127.0.0.1/moma", TlsMode::None, )?; for row in &conn.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(()) }
日期和时间
食谱 | 箱子 | 类别 |
---|---|---|
测量已用时间 | ||
执行,检查日期和时间的计算 | ||
将本地时间,转换为其他时区 | ||
检查日期和时间 | ||
将日期转换为 Unix 时间戳,或相反 | ||
显示格式化的日期和时间 | ||
将字符串解析为 DateTime 结构 |
持续时间和计算
Measure the elapsed time between two code sections
Measures time::Instant::elapsed
since time::Instant::now
.
Calling time::Instant::elapsed
returns a time::Duration
that we print at the end of the example.
This method will not mutate or reset the time::Instant
object.
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); }
Perform checked date and time calculations
Calculates and displays the date and time two weeks from now using
DateTime::checked_add_signed
and the date of the day before that using
DateTime::checked_sub_signed
. The methods return None if the date and time
cannot be calculated.
Escape sequences that are available for the
DateTime::format
can be found at chrono::format::strftime
.
extern crate chrono; 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."), } }
Convert a local time to another timezone
Gets the local time and displays it using offset::Local::now
and then converts it to the UTC standard using the DateTime::from_utc
struct method. A time is then converted using the offset::FixedOffset
struct and the UTC time is then converted to UTC+8 and UTC-2.
extern crate chrono; 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)); }
解析和显示
检查日期和时间
DateTime
获取当前的 UTC,还有Timelike
,能得到它的小时/分钟/秒(hour/minute/second),通过Datelike
,则能获得它的年/月/日/周末(year/month/day/weekday)。
extern crate chrono; 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 时间戳,反之亦然
NaiveDate::from_ymd
和NaiveTime::from_hms
给出一个日期,用NaiveDateTime::timestamp
转换到UNIX 时间戳。然后使用NaiveDateTime::from_timestamp
,它计算从 1970 1,01 0:00:00 UTC 开始,10 亿秒后的日期。
extern crate chrono; use chrono::{NaiveDate, NaiveDateTime}; fn main() { let date_time: NaiveDateTime = NaiveDate::from_ymd(2017, 11, 12).and_hms(17, 33, 44); println!( "Number of seconds between 1970-01-01 00:00:00 and {} is {}.", date_time, date_time.timestamp()); let date_time_after_a_billion_seconds = NaiveDateTime::from_timestamp(1_000_000_000, 0); println!( "Date after a billion seconds since 1970-01-01 00:00:00 was {}.", date_time_after_a_billion_seconds); }
显示格式化的日期和时间
使用Utc::now
,获取并显示当前时间(以 UTC 为单位)。以众所周知的格式RFC 2822,格式化当前时间,通过DateTime::to_rfc2822
。还有RFC 3339格式,可以使用DateTime::to_rfc3339
,除此之外,用DateTime::format
可以自定义格式。
extern crate chrono; use chrono::{DateTime, Utc}; fn main() { let now: DateTime<Utc> = Utc::now(); println!("UTC now is: {}", now); println!("UTC now in RFC 2822 is: {}", now.to_rfc2822()); println!("UTC now in RFC 3339 is: {}", now.to_rfc3339()); println!("UTC now in a custom format is: {}", now.format("%a %b %e %T %Y")); }
将字符串解析为 datetime 结构
解析已知的字符串表达格式RFC 2822,RFC 3339和自定义格式,将其变为一个DateTime
结构,可以分别使用DateTime::parse_from_rfc2822
,DateTime::parse_from_rfc3339
和DateTime::parse_from_str
。
可以在chrono::format::strftime
找到,转义序列,让其可用于DateTime::format
。 请注意DateTime::parse_from_str
要求一个 DateTime 结构,必须是创建的,唯一标识的日期和时间。请使用NaiveDate
,NaiveTime
和NaiveDateTime
,分析没有时区的日期和时间。
extern crate chrono; 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(()) }
开发工具
调试
食谱 | 箱子 | 类别 |
---|---|---|
将调试消息,记录到控制台 | ||
将错误消息,记录到控制台 | ||
记录到 stdout ,而不是 stderr | ||
使用自定义记录器,记录消息 | ||
记录到 Unix 系统日志 | ||
启用每个模块的日志级别 | ||
使用自定义环境变量,设置日志记录 | ||
在日志消息中,包含时间戳 | ||
将消息记录,到自定义位置 |
版本控制
食谱 | 箱子 | 类别 |
---|---|---|
解析,并增加版本字符串 | ||
分析,复杂版本字符串 | ||
检查给定版本,是否为预发布版本 | ||
查找,满足给定范围的最新版本 | ||
检查外部命令版本的兼容性 |
构建时
食谱 | 箱子 | 类别 |
---|---|---|
静态编译,并链接到捆绑的 C 库 | ||
编译,并链接到捆绑的 C++库 | ||
自定义设置时,编译 C 库 |
调试
食谱 | 箱子 | 类别 |
---|---|---|
将调试消息,记录到控制台 | ||
将错误消息,记录到控制台 | ||
记录到 stdout ,而不是 stderr | ||
使用自定义记录器,记录消息 | ||
记录到 Unix 系统日志 | ||
启用每个模块的日志级别 | ||
使用自定义环境变量,设置日志记录 | ||
在日志消息中,包含时间戳 | ||
将消息记录,到自定义位置 |
日志消息
将调试消息,记录到控制台
这个log
箱子提供记录工具。这个env_logger
箱子能通过环境变量,配置日志记录。这个debug!
宏的工作,与其他std::fmt
格式化字符串一样。
#[macro_use] extern crate log; extern crate env_logger; fn execute_query(query: &str) { 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
的方便宏error!
,把错误日志记录到 stderr。
#[macro_use] extern crate log; extern crate env_logger; 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 { error!("Failed to execute query: {}", err); } }
登录到 stdout 而不是 stderr
使用Builder::target
创建一个自定义记录器配置,将日志输出的目标设置为Target::Stdout
。
#[macro_use] extern crate log; extern crate env_logger; use env_logger::{Builder, Target}; fn main() { Builder::new() .target(Target::Stdout) .init(); error!("This error has been printed to Stdout"); }
使用自定义记录器,记录消息
实现一个自定义记录器ConsoleLogger
,它打印到 stdout。为了使用记录的宏,ConsoleLogger
实现log::Log
trait,那么log::set_logger
就能安装它了。
#[macro_use] extern crate log; 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); info!("hello log"); warn!("warning"); error!("oops"); Ok(()) }
登录到 Unix 系统日志
将消息记录到UNIX 系统日志。用syslog::init
初始化记录器后端。 syslog::Facility
表明该程序,添加的日志条目分类,log::LevelFilter
表示允许的日志等级,和Option<&str>
持有可选的应用程序名称。
#[macro_use] extern crate log; # #[cfg(target_os = "linux")] extern crate syslog; # #[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"))?; debug!("this is a debug {}", "message"); error!("this is an error!"); Ok(()) } # #[cfg(not(target_os = "linux"))] # fn main() { # println!("So far, only Linux systems are supported."); # }
配置日志记录
启用每个模块的日志级别
创建两个模块:foo
,以及嵌套的foo::bar
,它们的记录指令,单独用RUST_LOG
环境变量控制。
#[macro_use] extern crate log; extern crate env_logger; mod foo { mod bar { pub fn run() { warn!("[bar] warn"); info!("[bar] info"); debug!("[bar] debug"); } } pub fn run() { warn!("[foo] warn"); info!("[foo] info"); debug!("[foo] debug"); bar::run(); } } fn main() { env_logger::init(); warn!("[root] warn"); info!("[root] info"); debug!("[root] debug"); foo::run(); }
RUST_LOG
环境变量,控制env_logger
输出。模块声明采用逗号分隔项,格式如下path::to::module=log_level
。 运行test
应用如下:
RUST_LOG="warn,test::foo=info,test::foo::bar=debug" ./test
设置默认值log::Level
为warn
,而模块foo
和模块foo::bar
则分别为info
和debug
。
WARN:test: [root] warn
WARN:test::foo: [foo] warn
INFO:test::foo: [foo] info
WARN:test::foo::bar: [bar] warn
INFO:test::foo::bar: [bar] info
DEBUG:test::foo::bar: [bar] debug
使用自定义环境变量,设置日志记录
Builder
配置日志记录。
Builder::parse
解析MY_APP_LOG
环境变量内容,变为RUST_LOG
语法形式。然后,Builder::init
初始化记录器。所有这些步骤,通常都是由env_logger::init
内部完成。
#[macro_use] extern crate log; extern crate env_logger; use std::env; use env_logger::Builder; fn main() { Builder::new() .parse(&env::var("MY_APP_LOG").unwrap_or_default()) .init(); info!("informational message"); warn!("warning message"); error!("this is an error {}", "message"); }
在日志消息中,包含时间戳
使用Builder
创建一个自定义记录器配置。 每次记录都调用Local::now
,获取在本地时区的当前DateTime
,和使用DateTime::format
带上strftime::specifiers
,在最终记录中,格式化使用的时间戳。
示例:调用Builder::format
,它需要设置一个闭包函数,对每个消息文本格式化,添加时间戳,Record::level
和主体Record::args
)。
#[macro_use] extern crate log; extern crate chrono; extern crate env_logger; 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(); warn!("warn"); info!("info"); debug!("debug"); }
stderr 输出,将包含
2017-05-22T21:57:06 [WARN] - warn
2017-05-22T21:57:06 [INFO] - info
将消息记录,到自定义位置
log4rs能配置日志记录,输出到自定义的位置。log4rs可以使用外部 yaml 文件,或一个生成器配置。
使用log4rs::append::file::FileAppender
创建一个日志配置。该配置继续使用来自log4rs::encode::pattern
的自定义模式,进行编码,要知道,这个配置其实是定义日录记录目标的附加项。待会,还要将附加项配置,分配给log4rs::config::Config
,并设置默认值log::LevelFilter
。
# #[macro_use] # extern crate error_chain; #[macro_use] extern crate log; extern crate log4rs; 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 run() -> 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)?; info!("Hello, world!"); Ok(()) } # # quick_main!(run);
版本控制
分析并增加版本字符串。
构建一个semver::Version
从字符串文本使用Version::parse
,然后逐个递增补丁、次要版本和主要版本号。
注意,根据[语义版本规范],递增次要版本号将补丁版本号重置为0,递增主要版本号将次要版本号和补丁版本号重置为0。
extern crate semver; use semver::{Version, SemVerError}; fn main() -> Result<(), SemVerError> { let mut parsed_version = Version::parse("0.2.6")?; assert_eq!( parsed_version, Version { major: 0, minor: 2, patch: 6, pre: vec![], build: vec![], } ); parsed_version.increment_patch(); assert_eq!(parsed_version.to_string(), "0.2.7"); println!("New patch release: v{}", parsed_version); parsed_version.increment_minor(); assert_eq!(parsed_version.to_string(), "0.3.0"); println!("New minor release: v{}", parsed_version); parsed_version.increment_major(); assert_eq!(parsed_version.to_string(), "1.0.0"); println!("New major release: v{}", parsed_version); Ok(()) }
分析复杂的版本字符串。
使用Version::parse
从复杂版本字符串,构建一个semver::Version
。 该字符串包含预发布和构建的元数据,正如[语义版本规范]所定义的。
注意,根据规范,构建元数据虽然解析了,但在比较版本时,不考虑。换句话说,即使构建字符串不同,两个版本也可能相同。
extern crate semver; use semver::{Identifier, Version, SemVerError}; fn main() -> Result<(), SemVerError> { let version_str = "1.0.49-125+g72ee7853"; let parsed_version = Version::parse(version_str)?; assert_eq!( parsed_version, Version { major: 1, minor: 0, patch: 49, pre: vec![Identifier::Numeric(125)], build: vec![], } ); assert_eq!( parsed_version.build, vec![Identifier::AlphaNumeric(String::from("g72ee7853"))] ); let serialized_version = parsed_version.to_string(); assert_eq!(&serialized_version, version_str); Ok(()) }
检查给定版本,是否为预发布版本。
给出两个版本,is_prerelease
断言一个是预发布,另一个不是。
extern crate semver; use semver::{Version, SemVerError}; fn main() -> Result<(), SemVerError> { let version_1 = Version::parse("1.0.0-alpha")?; let version_2 = Version::parse("1.0.0")?; assert!(version_1.is_prerelease()); assert!(!version_2.is_prerelease()); Ok(()) }
查找满足给定范围的最新版本
给定版本 &str
的一个列表,查找最新的semver::Version
。 semver::VersionReq
能用VersionReq::matches
筛选列表。还可以,给出预发布semver
的首选项。
# #[macro_use] # extern crate error_chain; extern crate semver; 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 run() -> 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(()) } # # quick_main!(run);
检查外部命令版本的兼容性
使用Command
运行git --version
,然后用Version::parse
,将版本号解析为semver::Version
。 VersionReq::matches
会比较semver::VersionReq
与已解析的版本。命令输出,类似于”git version x.y.z”。
# #[macro_use] # extern crate error_chain; extern crate semver; 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 run() -> 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() { 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) { bail!("Command version lower than minimum supported version (found {}, need {})", parsed_version, version_constraint); } Ok(()) } # # quick_main!(run);
构建时工具
本节介绍在编译箱子源代码之前,运行的“构建时”工具或代码。通常,构建时代码,位于build.rs文件,通常称为“构建脚本”。常见的用例,包括:Rust 代码生成,和捆绑的 C/C++/ASM 代码的编译。查看 crates.io 的 主分支文档,了解更多信息。
编译,并静态链接到捆绑的 C 库
为了适应项目,需要附加 C、C++ 或 ASM 的场景,cc箱子,提供了一个简单的 API,用于将捆绑的 C/C++/ASM 代码编译成静态库(.a),这样就可以静态链接到rustc.
下面的示例,包含一些绑定的 C 代码(src/hello.c),会用在 Rust 上。在编译 rust 源代码之前,“构建”文件(build.rs)可通过Cargo.toml指定运行。若使用cc箱子,一个静态库文件将被生成(在这情况下,就是libhello.a,若想知道更多,请看compile
文档)然后,在 Rust 中想使用该库,可以通过在extern
块中,定义外部函数签名。
由于绑定的 C 非常简单,因此只需要将一个源文件,传递给cc::Build
。 对于更复杂的构建需求,cc::Build
为指定的include
的路径,和额外的编译器flag
标志们,提供了一整套构建器方法。
Cargo.toml
[package]
...
build = "build.rs"
[build-dependencies]
cc = "1"
[dependencies]
error-chain = "0.11"
build.rs
extern crate cc; fn main() { cc::Build::new() .file("src/hello.c") .compile("hello"); // outputs `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
# #[macro_use] extern crate 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 run() -> Result<()> {
unsafe { hello() }
let name = prompt("What's your name? ")?;
let c_name = CString::new(name)?;
unsafe { greet(c_name.as_ptr()) }
Ok(())
}
#
# quick_main!(run);
编译,并静态链接到捆绑的 C++库
链接捆绑的 C++库,非常类似于链接捆绑的 C 库。编译和静态链接捆绑的 C++库的两个核心区别,是通过构建器方法cpp(true)
指定 C++编译器。通过在我们的 C++源文件的顶部,添加extern "C"
部分,防止 C++编译器的名称篡改。
Cargo.toml
[package]
...
build = "build.rs"
[build-dependencies]
cc = "1"
build.rs
extern crate cc; fn main() { cc::Build::new() .cpp(true) .file("src/foo.cpp") .compile("foo"); }
src/foo.cpp
extern "C" {
int multiply(int x, int y);
}
int multiply(int x, int y) {
return x*y;
}
src/main.rs
extern {
fn multiply(x : i32, y : i32) -> i32;
}
fn main(){
unsafe {
println!("{}", multiply(5,7));
}
}
带自定义设置,编译 C 库
使用cc::Build::define
自定义构建,捆绑的 C 代码很简单。 该方法采用Option
值,因此可以创建如下定义:#define APP_NAME "foo"
,以及#define WELCOME
(传递 None
作为一个缺乏值的定义)。这个例子构建了一个动态定义集的捆绑 C 文件,而定义集在build.rs
中,并会在运行使,打印欢迎使用 foo-版本 1.0.2“。Cargo 会设置一些环境变量,这对于某些自定义设置可能很有用。
Cargo.toml
[package]
...
version = "1.0.2"
build = "build.rs"
[build-dependencies]
cc = "1"
build.rs
extern crate cc; 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();
}
}
编码
字符集
URL 编码字符串
编码一个输入字符串,编码方式为URL 编码,可通过使用来自url
箱子的utf8_percent_encode
函数完成。然后再使用percent_decode
函数。
extern crate url; use url::percent_encoding::{utf8_percent_encode, percent_decode, DEFAULT_ENCODE_SET}; use std::str::Utf8Error; fn main() -> Result<(), Utf8Error> { let input = "confident, productive systems programming"; let iter = utf8_percent_encode(input, DEFAULT_ENCODE_SET); 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
切片的迭代器,这能收集(collect)成一个String
。
将字符串编码为 application/x-www-form-urlencoded
将字符串,编码为application/x-www-form-urlencoded语法,通过form_urlencoded::byte_serialize
,然后用form_urlencoded::parse
完成。 两个函数都返回迭代器,这些迭代器能收集成一个String
。
extern crate url; 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
箱子提供一个HEXUPPER::encode
方法,它会获取一个&[u8]
,并返回一个String
,其中包含数据的十六进制表示形式。
类似地,一个HEXUPPER::decode
提供的方法,也获取一个&[u8]
,并如果输入数据解码成功的话,就返回一个Vec<u8>
。
下面的例子,将&[u8]
数据转换为等效十六进制。将此值与预期值,进行比较。
extern crate data_encoding; 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
字符串,通过encode
完成,还能用decode
解码。
# #[macro_use] # extern crate error_chain; extern crate base64; use std::str; use base64::{encode, decode}; # # error_chain! { # foreign_links { # Base64(base64::DecodeError); # Utf8Error(str::Utf8Error); # } # } fn run() -> 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(()) } # # quick_main!(run);
CSV 处理
读取 csv 记录
将标准 csv 记录,读取到csv::StringRecord
:一种弱类型数据表示形式,这需要有效的 UTF-8 行。或者,csv::ByteRecord
,它不会对 UTF-8 有任何假设。
extern crate csv; 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
方法。
extern crate csv; # #[macro_use] # extern crate error_chain; #[macro_use] extern crate serde_derive; # error_chain! { # foreign_links { # Reader(csv::Error); # } # } # #[derive(Deserialize)] struct Record { year: u16, make: String, model: String, description: String, } fn run() -> Result<()> { 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(()) } # # quick_main!(run);
读取具有不同分隔符的 csv 记录
用一个 tab(分隔符) delimiter
读取 csv 记录。
extern crate csv; use csv::Error; #[macro_use] extern crate serde_derive; #[derive(Debug, Deserialize)] struct Record { name: String, place: String, #[serde(deserialize_with = "csv::invalid_option")] id: Option<u64>, } use csv::ReaderBuilder; fn main() -> Result<(), Error> { let data = "name\tplace\tid Mark\tMelbourne\t46 Ashley\tZurich\t92"; let mut reader = ReaderBuilder::new().delimiter(b'\t').from_reader(data.as_bytes()); for result in reader.deserialize::<Record>() { println!("{:?}", result?); } Ok(()) }
筛选与断言匹配的 csv 记录
只返回data
中字段(field)行,匹配query
的。
# #[macro_use] # extern crate error_chain; extern crate csv; use std::io; # # error_chain!{ # foreign_links { # Io(std::io::Error); # CsvError(csv::Error); # } # } fn run() -> 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(()) } # # quick_main!(run);
_免责声明:本示例改编自the csv crate tutorial*.
使用 serde
处理无效的 csv 数据
csv 文件通常包含无效数据。对于这些情况,csv
箱子提供一个自定义反序列化程序,csv::invalid_option
,自动将无效数据转换为 None
值。
extern crate csv; use csv::Error; #[macro_use] extern crate serde_derive; #[derive(Debug, Deserialize)] struct Record { name: String, place: String, #[serde(deserialize_with = "csv::invalid_option")] id: Option<u64>, } fn main() -> Result<(), Error> { let data = "name,place,id mark,sydney,46.5 ashley,zurich,92 akshat,delhi,37 alisha,colombo,xyz"; let mut rdr = csv::Reader::from_reader(data.as_bytes()); for result in rdr.deserialize() { let record: Record = result?; println!("{:?}", record); } Ok(()) }
将记录,序列化为 csv
这个例子:演示了如何序列化一个 Rust 元组。csv::writer
支持从 Rust 类型,到 csv 记录的自动序列化。write_record
只写入包含字符串数据的简单记录。对于具有更复杂值(如数字、浮点数和选项)的数据,请使用serialize
。因为 csv 编写器使用内部缓冲区,所以在做完之后,始终要显式flush
。
# #[macro_use] # extern crate error_chain; extern crate csv; use std::io; # # error_chain! { # foreign_links { # CSVError(csv::Error); # IOError(std::io::Error); # } # } fn run() -> 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(()) } # # quick_main!(run);
使用 serde 将记录序列化为 csv
下面的示例,演示如何使用serde箱子。
# #[macro_use] # extern crate error_chain; extern crate csv; #[macro_use] extern crate serde_derive; 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 run() -> 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(()) } # # quick_main!(run);
转换 csv 列
将包含颜色名称和十六进制颜色的 csv 文件,转换为具有颜色名称和 RGB 颜色的文件。利用csv箱子,读取和写入 csv 文件,以及serde对文件的一行,在字节之间进行反序列化和序列化。
见csv::Reader::deserialize
,serde::Deserialize
和std::str::FromStr
extern crate csv; # #[macro_use] # extern crate error_chain; #[macro_use] extern crate serde_derive; extern crate serde; 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 run() -> 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(()) } # # quick_main!(run);
结构化数据
序列化和反序列化,非结构化 JSON
这个serde_json
箱子提供from_str
这个解析函数,它需要 JSON 的&str
。
非结构化 JSON 可以解析为,一个通用的serde_json::Value
类型,它能够表示任何有效 JSON 数据的类型。
下面的示例显示,正在解析 JSON 的 &str
。使用json!
宏:
#[macro_use] extern crate serde_json; use serde_json::{Value, Error}; fn main() -> Result<(), Error> { let j = r#"{ "userid": 103609, "verified": true, "access_privileges": [ "user", "admin" ] }"#; let parsed: Value = serde_json::from_str(j)?; let expected = json!({ "userid": 103609, "verified": true, "access_privileges": [ "user", "admin" ] }); assert_eq!(parsed, expected); Ok(()) }
反序列化,toml 配置文件
把一些 toml 解析成,一个通用的toml::Value
类型,它能够表示任何有效的 TOML 数据。
extern crate toml; use toml::{Value, de::Error}; fn main() -> Result<(), Error> { let toml_content = r#" [package] name = "your_package" version = "0.1.0" authors = ["You! <you@example.org>"] [dependencies] serde = "1.0" "#; let package_info: Value = toml::from_str(toml_content)?; assert_eq!(package_info["dependencies"]["serde"].as_str(), Some("1.0")); assert_eq!(package_info["package"]["name"].as_str(), Some("your_package")); Ok(()) }
使用serde,将 toml 解析为您自己的结构。
# #[macro_use] # extern crate error_chain; #[macro_use] extern crate serde_derive; extern crate toml; 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>, } # # error_chain! { # foreign_links { # Toml(toml::de::Error); # } # } fn run() -> Result<()> { let toml_content = r#" [package] name = "your_package" version = "0.1.0" authors = ["You! <you@example.org>"] [dependencies] serde = "1.0" "#; let package_info: Config = toml::from_str(toml_content)?; assert_eq!(package_info.package.name, "your_package"); assert_eq!(package_info.package.version, "0.1.0"); assert_eq!(package_info.package.authors, vec!["You! <you@example.org>"]); assert_eq!(package_info.dependencies["serde"], "1.0"); Ok(()) } # # quick_main!(run);
以小端序,读取和写入整数
byteorder
可以反转,结构化数据的有效字节。这对网络上的信息接收,还有类似另一个系统的字节来说,可能会有必要。
extern crate 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) }
错误处理
食谱 | 箱子 | 类别 |
---|---|---|
main 的 正确处理错误 | ||
避免在错误转换期间,丢弃错误 | ||
获取错误复杂场景的回溯 |
错误处理
main 中的正确处理错误
要处理尝试打开不存在的文件时,发生的错误。我们会用到error-chain,这是一个大量样板代码的库,用来[处理 Rust 的错误]。
foreign_links
里面的Io(std::io::Error)
,允许从std::io::Error
到error_chain!
所定义类型的自动转换,这些类型会实现Error
trait。
下面的食谱是,在打开 unix 文件/proc/uptime
,然后分析内容得到第一个数字期间,告诉我们系统运行了多长时间。要返回 uptime,除非出现错误。
本书中的其他食谱,会隐藏error-chain样板文件,可以通过按钮(展开代码)来查看。
#[macro_use] extern crate 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箱子,能匹配函数返回的不同错误类型,可能是相对紧凑的。ErrorKind
能确定错误类型。
使用reqwest查询一个随机整数生成器的 Web 服务。将响应的字符串,转换为整数。我们有 Rust 标准库,reqwest,并且 Web 服务的全部错误都会(可能)发生。要明确定义的 Rust 错误,请使用foreign_links
。 对于额外的 Web 服务错误ErrorKind
变种,使用error_chain!
宏的errors
代码块。
#[macro_use] extern crate error_chain; extern crate reqwest; use std::io::Read; error_chain! { foreign_links { Io(std::io::Error); Reqwest(reqwest::Error); ParseIntError(std::num::ParseIntError); } errors { RandomResponseError(t: String) } } fn parse_response(mut response: reqwest::Response) -> Result<u32> { let mut body = String::new(); response.read_to_string(&mut body)?; 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::get(&url)?; let random_value: u32 = parse_response(response)?; println!("a random number between 0 and 10: {}", random_value); Ok(()) } fn main() { if let Err(error) = run() { match *error.kind() { ErrorKind::Io(_) => println!("Standard IO error: {:?}", error), ErrorKind::Reqwest(_) => println!("Reqwest error: {:?}", error), ErrorKind::ParseIntError(_) => println!("Standard parse int error: {:?}", error), ErrorKind::RandomResponseError(_) => println!("User defined error: {:?}", error), _ => println!("Other error: {:?}", error), } } }
获取复杂的错误场景的回溯
这个食谱,演示了如何处理复杂的错误场景,然后打印回溯。它依赖chain_err
附加新错误,来扩展错误(信息)。错误(信息)栈可以展开,从而提供更好的上下文,来理解引发错误的原因。
以下食谱,尝试将值256
反序列化成一个u8
。一个错误从 serde 开始冒泡,然后是 csv,最后到达,用户代码。
# extern crate csv; #[macro_use] extern crate error_chain; # #[macro_use] # extern crate serde_derive; # # 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
,显示与此错误关联的backtrace
的详细信息。
文件系统
食谱 | 箱子 | 类别 |
---|---|---|
从文件中,读取字符串行 | ||
避免写入和读取,同一文件 | ||
随机使用内存映射,访问文件 | ||
过去 24 小时内,修改过的文件名 | ||
查找给定路径的循环 | ||
递归查找,重复的文件名 | ||
递归查找,具有给定断言的所有文件 | ||
跳过点(隐藏)文件,遍历目录 | ||
在给定深度(目录),递归计算文件大小 | ||
递归查找,所有 PNG 文件 | ||
查找具有给定模式的所有文件,忽略文件名大小写 |
读和写
从文件中,读取字符串行
将一份三行的消息写入文件,然后由BufRead::lines
,返回Lines
迭代器,用来一次读取一行。 File
实现了Read
,也就是提供BufReader
trait。File::create
会打开一个File
,用于写入(文件),而File::open
用来读取。
use std::fs::File; use std::io::{Write, BufReader, BufRead, Error}; fn main() -> Result<(), Error> { let path = "lines.txt"; let mut output = File::create(path)?; write!(output, "Rust\n💖\nFun")?; let input = File::open(path)?; let buffered = BufReader::new(input); for line in buffered.lines() { println!("{}", line?); } Ok(()) }
避免写入和读取,同一文件
使用same_file::Handle
控制一个文件,这样,该文件可以测试是否与其他 Handle(控制)的文件相等。在这个例子中,要读取和写入的文件 handle,被测试是否相等。
extern crate same_file; use same_file::Handle; use std::fs::File; use std::io::{BufRead, BufReader, Error, ErrorKind}; use std::path::Path; fn main() -> Result<(), Error> { let path_to_read = Path::new("new.txt"); let stdout_handle = Handle::stdout()?; let handle = Handle::from_path(path_to_read)?; if stdout_handle == handle { return Err(Error::new( ErrorKind::Other, "You are reading and writing to the same file", )); } else { let file = File::open(&path_to_read)?; let file = BufReader::new(file); for (num, line) in file.lines().enumerate() { println!("{} : {}", num, line?.to_uppercase()); } } Ok(()) }
cargo run
显示文件 new.txt
的内容。
cargo run >> ./new.txt
错误,因为两个文件相同。
使用内存映射,随机访问文件
使用memmap创建文件的内存映射,并模拟一些文件的非顺序读取。使用内存映射,意味着您只需要索引到一个切片,而不是处理导航文件的seek
。
这个Mmap::map
函数假定内存映射后面的文件,不会同时被另一个进程修改,或者竞争条件发生。
extern crate memmap; 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 小时内,修改过的文件名
通过调用env::current_dir
,获得当前工作目录,然后针对fs::read_dir
中的每个条目,提取DirEntry::path
,并通过fs::Metadata
得到元信息。 这个Metadata::modified
返回上次修改后的SystemTime::elapsed
时间。Duration::as_secs
会将时间转换为秒,并与 24 小时(24 _ 60 _ 60 秒)比较。Metadata::is_file
则是筛选出目录。
# #[macro_use] # extern crate error_chain; # use std::{env, fs}; # error_chain! { # foreign_links { # Io(std::io::Error); # SystemTimeError(std::time::SystemTimeError); # } # } # fn run() -> 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(()) } # # quick_main!(run);
查找给定路径的循环
使用same_file::is_same_file
检测给定路径的循环。例如,可以通过符号链接,在 UNIX 系统上,创建一个循环:
mkdir -p /tmp/foo/bar/baz
ln -s /tmp/foo/ /tmp/foo/bar/baz/qux
/tmp/foo/bar/baz/qux == /tmp/foo/ => /tmp/foo/bar/baz => /tmp/foo/bar/baz/qux (重复循环)
下面将断言,存在一个循环。
extern crate same_file; 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") )) ); }
递归查找,重复的文件名
在当前目录中,递归查找重复的文件名,只打印一次。
extern crate walkdir; use std::collections::HashMap; use walkdir::WalkDir; fn main() { let mut filenames = HashMap::new(); for entry in WalkDir::new(".") .into_iter() .filter_map(Result::ok) .filter(|e| !e.file_type().is_dir()) { let f_name = String::from(entry.file_name().to_string_lossy()); let counter = filenames.entry(f_name.clone()).or_insert(0); *counter += 1; if *counter == 2 { println!("{}", f_name); } } }
递归查找所有文件,匹配给定断言
在当前目录中查找,在前一天内有修改的 JSON 文件。使用follow_links
确保,符号链接像普通目录和文件一样被遵循。
# #[macro_use] # extern crate error_chain; extern crate walkdir; use walkdir::WalkDir; # # error_chain! { # foreign_links { # WalkDir(walkdir::Error); # Io(std::io::Error); # SystemTime(std::time::SystemTimeError); # } # } fn run() -> 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(()) } # # quick_main!(run);
跳过点(隐藏)文件时,遍历目录
使用filter_entry
深度递归,传递的是is_not_hidden
断言,因此跳过隐藏的文件和目录。Iterator::filter
应用到每个WalkDir::DirEntry
,即使父目录是隐藏目录。
根目录"."
的结果输出,是通过is_not_hidden
断言中WalkDir::depth
的使用。
extern crate walkdir; use walkdir::{DirEntry, WalkDir}; fn is_not_hidden(entry: &DirEntry) -> bool { entry .file_name() .to_str() .map(|s| entry.depth() == 0 || !s.starts_with(".")) .unwrap_or(false) } fn main() { WalkDir::new(".") .into_iter() .filter_entry(|e| is_not_hidden(e)) .filter_map(|v| v.ok()) .for_each(|x| println!("{}", x.path().display())); }
在给定深度(目录),递归计算文件大小
递归一定深度的目录,可以通过以下方式灵活设置:WalkDir::min_depth
& WalkDir::max_depth
方法。 3 个子文件夹深度的所有文件,其总和大小的计算,忽略根文件夹中的文件。
extern crate walkdir; use walkdir::WalkDir; fn main() { let total_size = WalkDir::new(".") .min_depth(1) .max_depth(3) .into_iter() .filter_map(|entry| entry.ok()) .filter_map(|entry| entry.metadata().ok()) .filter(|metadata| metadata.is_file()) .fold(0, |acc, m| acc + m.len()); println!("Total size: {} bytes.", total_size); }
递归查找所有 PNG 文件
递归查找,当前目录中的所有 PNG 文件。在这种情况下,**
模式匹配当前目录和所有子目录。
可在任何路径部分,使用**
模式。例如,/media/**/*.png
,会匹配media
下的所有 PNG,且是子目录。
# #[macro_use] # extern crate error_chain; extern crate glob; use glob::glob; # # error_chain! { # foreign_links { # Glob(glob::GlobError); # Pattern(glob::PatternError); # } # } fn run() -> Result<()> { for entry in glob("**/*.png")? { println!("{}", entry?.display()); } Ok(()) } # # quick_main!(run);
查找具有给定模式的所有文件,忽略文件名大小写。
在/media/
目录中查找所有图像文件,要匹配img_[0-9]*.png
模式。
一个自定义的MatchOptions
结构,传递给glob_with
函数,可以使全局模式不区分大小写,同时保留其他选项的默认Default
。
# #[macro_use] # extern crate error_chain; extern crate glob; use glob::{glob_with, MatchOptions}; # # error_chain! { # foreign_links { # Glob(glob::GlobError); # Pattern(glob::PatternError); # } # } fn run() -> Result<()> { let options = MatchOptions { case_sensitive: false, ..Default::default() }; for entry in glob_with("/media/img_[0-9]*.png", &options)? { println!("{}", entry?.display()); } Ok(()) } # # quick_main!(run);
硬件支持
食谱 | 箱子 | 类别 |
---|---|---|
检查逻辑 CPU 的核数 |
处理器
检查逻辑 CPU 的核数
[num_cpus::get
]会显示当前计算机中,使用的逻辑 CPU 核数。
extern crate num_cpus; fn main() { println!("Number of logical cores is {}", num_cpus::get()); }
内存管理
食谱 | 箱子 | 类别 |
---|---|---|
声明,延迟计算的常量 |
常量
声明,延迟计算的常量
声明一个延迟计算的HashMap
常量。 这个HashMap
会执行一次,在之后,会存储在全局的静态引用(PRIVILEGES)背后。
#[macro_use] extern crate 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"); }
网络
食谱 | 箱子 | 类别 |
---|---|---|
侦听,未使用的端口 TCP/IP |
服务器
侦听,未使用的端口 TCP/IP
在本例中,端口会显示在控制台。且程序会一直监听,直到一个请求发出。SocketAddrV4
会在端口(第二参数)设为 0 时,分配一个随机端口。
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()?; //block until requested 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(()) }
操作系统
食谱 | 箱子 | 类别 |
---|---|---|
运行外部命令,并处理 stdout | ||
运行传递到 stdin 的外部命令,并检查错误代码 | ||
运行管道的外部命令 | ||
将子进程的 stdout 和 stderr ,重定向到同一文件 | ||
连续处理,子进程的输出 |
外部命令
运行外部命令,并处理 stdout
把git log --oneline
,作为一个外部Command
,并检查其Output
,和用Regex
获取最后 5 次提交的哈希与消息。
# #[macro_use] # extern crate error_chain; extern crate regex; 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 run() -> Result<()> { let output = Command::new("git").arg("log").arg("--oneline").output()?; if !output.status.success() { bail!("Command executed with failing error code"); } let pattern = Regex::new(r"(?x) ([0-9a-fA-F]+) # commit hash (.*) # The commit message")?; 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(()) } # # quick_main!(run);
运行传递 stdin 的外部命令,并检查错误代码
使用外部Command
,打开python
解释程序,并将 python (语言)语句传递给它,以供执行。之后的输出Output
会被解析。
# #[macro_use] # extern crate 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 run() -> 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)?; bail!("External command failed:\n {}", err) } } # # quick_main!(run);
运行管道的外部命令
显示,当前工作目录中,10个最大的文件和子目录。它相当于运行:du -ah . | sort -hr | head -n 10
。
Command
们代表一个过程。一个子进程的输出用Stdio::piped
,在父级和子级之间捕获。
# #[macro_use] # extern crate error_chain; # use std::process::{Command, Stdio}; # # error_chain! { # foreign_links { # Io(std::io::Error); # Utf8(std::string::FromUtf8Error); # } # } fn run() -> 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(()) } # # quick_main!(run);
将子进程的 stdout 和 stderr ,重定向到同一文件
生成(Spawns)一个子进程,并重定向stdout
和stderr
到同一个文件。它与运行管道的外部命令差不多的想法,但会用process::Stdio
,把输出写入到指定的文件。File::try_clone
引用相同文件的stdout
和stderr
控制(Handle)。它将确保两个 Handles 使用相同的光标位置写入。
下面的食谱,相当于运行 unix shell 命令:ls . oops >out.txt 2>&1
.
use std::fs::File; use std::io::Error; use std::process::{Command, Stdio}; fn main() -> Result<(), Error> { let outputs = File::create("out.txt")?; let errors = outputs.try_clone()?; Command::new("ls") .args(&[".", "oops"]) .stdout(Stdio::from(outputs)) .stderr(Stdio::from(errors)) .spawn()? .wait_with_output()?; Ok(()) }
连续处理,子进程的输出
在运行外部命令,并处理 stdout食谱中,stdout
的处理会在外部Command
完成之后执行。而本食谱接下来的方法是,调用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(()) }
科学类
数学
食谱 | 箱子 | 类别 |
---|---|---|
vector 和 | ||
vector 范数 | ||
矩阵相加 | ||
矩阵乘法 | ||
用 vector 和矩阵,相乘一个标量 | ||
反转矩阵 | [![nalgebra-badge]][nalgebra] | |
计算三角形的边长 | ||
验证 tan 等于 sin 除以 cos | ||
地球两点之间的距离 | ||
创建复数 | ||
复数相加 | ||
复数的数学函数 | ||
集中趋势度量 | ||
计算标准偏差 | ||
大整数 |
数学
食谱 | 箱子 | 类别 |
---|---|---|
vector 和 | ||
vector 范数 | ||
矩阵相加 | ||
矩阵乘法 | ||
用 vector 和矩阵,相乘一个标量 | ||
反转矩阵 | [![nalgebra-badge]][nalgebra] | |
计算三角形的边长 | ||
验证 tan 等于 sin 除以 cos | ||
地球两点之间的距离 | ||
创建复数 | ||
复数相加 | ||
复数的数学函数 | ||
集中趋势度量 | ||
计算标准偏差 | ||
大整数 |
线性代数
vector 总和
这个ndarray箱子支持多种创建数组的方法 —— 该食谱主要专注std::Vec
的ndarray::Array
的创建,通过from_vec
完成。把两个数组加在一起和把两个数字加在一起,没有什么不同。在数组上,使用&
符号,能在一次算术运算中,阻止消耗数组的操作。没有&
的话,数组会被消耗。
在第一个示例中,数组a
和b
在 let 语句(z = a + b
)中移动:。 在第二个示例中,数组c
和d
不会移动,而是为w
创建一个新的数组。在 vector 总和(w = &c + &d
)之后,更新c
或d
,对w
值是没有影响的。另外,在打印c
时按预期工作,打印b
时出错,因为移动了。见两个数组的二元运算符更多细节。
extern crate ndarray; use ndarray::Array; fn main() { let a = Array::from_vec(vec![1., 2., 3., 4., 5.]); let b = Array::from_vec(vec![5., 4., 3., 2., 1.]); let mut c = Array::from_vec(vec![1., 2., 3., 4., 5.]); let mut d = Array::from_vec(vec![5., 4., 3., 2., 1.]); let z = a + b; let w = &c + &d; let epsilon = 1e-8; for elem in z.iter() { let diff: f32 = *elem - 6.; assert!(diff.abs() < epsilon); } println!("c = {}", c); c[0] = 10.; d[1] = 10.; for elem in w.iter() { let diff: f32 = *elem - 6.; assert!(diff.abs() < epsilon); } }
vector 范数
这个食谱演示了Array1
类型,ArrayView1
类型,fold
方法,以及dot
方法,它计算给定 vector 的l1和l2范数。l2 范数的计算是两者中,比较简单的一个,因为它是一个 vector 的点乘积的平方根,如l2_norm
函数所示。而 l1 范数,在l1_norm
函数,由fold
方法完成,它是元素绝对值的总和。(这也可以用x.mapv(f64::abs).scalar_sum()
执行,但这将为mapv
的结果分配一个新的数组。)
注意l1_norm
和l2_norm
都拿了ArrayView1
类型。这个食谱考虑了 vector 范数,因此范数函数,只需要接受一维 view(就是ArrayView1
)。当这个函数换成接受一个&Array1<f64>
类型参数,这将要求调用者具有,所有权数组的一个引用,这比仅对一个 view 的访问,更为严格(因为一个 view,是可以通过任何 array 或 view 创建的,而不仅仅是所有权数组)。对调用者来说,最方便的参数类型是&ArrayBase<S, Ix1> where S: Data
,因为调用者可以使用&array
或&view
,而不是x.view()
。 如果该函数是你公共 API 的一部分,那么对于用户来说,这可能是一个更好的选择,但是对于内部函数来说,简洁的ArrayView1<f64>
可能更好。
#[macro_use(array)] extern crate ndarray; use ndarray::{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)); }
矩阵相加
ndarray::arr2
创建两维矩阵,并把它们加在一起。
extern crate ndarray; use ndarray::arr2; fn main() { let a = arr2(&[[1, 2, 3], [4, 5, 6]]); let b = arr2(&[[6, 5, 4], [3, 2, 1]]); println!("Sum: {}", a + b); }
矩阵乘法
ndarray::arr2
创建两个矩阵,并用ndarray::ArrayBase::dot
对它们执行矩阵乘法。
extern crate ndarray; use ndarray::arr2; fn main() { let a = arr2(&[[1, 2, 3], [4, 5, 6]]); let b = arr2(&[[6, 3], [5, 2], [4, 1]]); println!("{}", a.dot(&b)); }
用 vector 和 矩阵,乘一个标量
用ndarray::arr1
创建一个,一维数组(vector),二维数组(矩阵)就用ndarray::arr2
。 首先,将一个标量乘以 vector ,得到另一个 vector。然后,矩阵用ndarray::Array2::dot
,乘以这个新的 vector。 (dot
会执行矩阵乘法,而*
运算符scalar * vector
,执行逐个元素的相乘。)在ndarray
中,一维数组根据上下文,可以解释为行或列 vector。如果 vector 表示的方向很重要,则要用二维数组的一行或一列。在本例中,vector 是右手边的一维数组,因此,dot
将其作为列 vector 处理。
extern crate ndarray; 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); }
反转矩阵
使用nalgebra::Matrix3
创建 3x3 矩阵,如果可能的话,将其反转。
extern crate nalgebra; 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!"); } } }
三角法
计算一个三角形的边长
计算直角三角形斜边的长度,斜边的角度为 2 弧度,对边的长度为 80。
fn main() { let angle: f64 = 2.0; let side_length = 80.0; let hypotenuse = side_length/angle.sin(); println!("Hypotenuse: {}", hypotenuse); }
验证 tan 等于,sin 除以 cos
验证 tan(x)
是否等于 sin(x)/cos(x)
,给出 x=6
。
fn main() { let x: f64 = 6.0; let a = x.tan(); let b = x.sin()/x.cos(); assert_eq!(a, b); }
地球上,两点之间的距离
默认情况下,rust 提供数学[浮点数方法],如三角函数、平方根、弧度和度数之间的转换函数等。
下面的示例使用半正矢公式,计算地球上两点的公里距离。这两个点用经纬度对表示。然后,to_radians
把它们转换成弧度。sin
,cos
,powi
和sqrt
计算中心角。最后,就可以计算距离。
fn main() { let earth_radius_kilometer = 6371.0_f64; let (paris_latitude_degrees, paris_longitude_degrees) = (48.85341_f64, -2.34880_f64); let (london_latitude_degrees, london_longitude_degrees) = (51.50853_f64, -0.12574_f64); let paris_latitude = paris_latitude_degrees.to_radians(); let london_latitude = london_latitude_degrees.to_radians(); let delta_latitude = (paris_latitude_degrees - london_latitude_degrees).to_radians(); let delta_longitude = (paris_longitude_degrees - london_longitude_degrees).to_radians(); let central_angle_inner = (delta_latitude/2.0).sin().powi(2) + paris_latitude.cos() * london_latitude.cos() * (delta_longitude/2.0).sin().powi(2); let central_angle = 2.0 * central_angle_inner.sqrt().asin(); let distance = earth_radius_kilometer * central_angle; println!( "Distance between Paris and London on the surface of Earth is {:.1} kilometers", distance ); }
复数
创建复数
创建num::complex::Complex
类型的复数。复数的实部和虚部必须是同一类型。
extern crate num; 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); }
复数相加
对复数执行数学运算与内置类型相同:涉及的数字必须是同一类型(即浮点数或整数)。
extern crate num; fn main() { let complex_num1 = num::complex::Complex::new(10.0, 20.0); // Must use floats let complex_num2 = num::complex::Complex::new(3.1, -4.2); let sum = complex_num1 + complex_num2; println!("Sum: {}", sum); }
数学函数
当涉及如何与其他数学函数交互时,复数有一系列有趣的性质,尤其是类似数字的正弦家族函数。要将这些函数与复数一起使用,复数类型有一些内置函数,所有这些函数都可以找:num::complex::Complex
。
extern crate num; use std::f64::consts::PI; use num::complex::Complex; fn main() { let x = Complex::new(0.0, 2.0*PI); println!("e^(2i * pi) = {}", x.exp()); // =~1 }
统计
集中趋势度量
这些例子计算了包含在一个 Rust 数组中,数据集的集中趋势度量。对于空数据集,可能没有要计算的平均值、中值或常见值,因此,每个函数都返回一个给调用方处理的[Option
]。
第一个示例,计算平均值(集合中,总和/数量),通过生成对data
引用的迭代器,并使用[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); }
第二个示例使用 QuickSelect 算法,计算中间值,这避免了[sort
]的完整排序,只对已知可能包含中间值的数据集分区,进行排序。这用到[cmp
]和[Ordering
],简洁决定下一个要检查的分区,以及在每个步骤中,[split_at
]为下一个分区进行静态选择。
use std::cmp::Ordering; fn partition(data: &[i32]) -> Option<(Vec<i32>, i32, Vec<i32>)> { match data.len() { 0 => None, _ => { let (pivot_slice, tail) = data.split_at(1); let pivot = pivot_slice[0]; let (left, right) = tail.iter() .fold((vec![], vec![]), |mut splits, next| { { let (ref mut left, ref mut right) = &mut splits; if next < &pivot { left.push(*next); } else { right.push(*next); } } splits }); Some((left, pivot, right)) } } } fn select(data: &[i32], k: usize) -> Option<i32> { let part = partition(data); match part { None => None, Some((left, pivot, right)) => { let pivot_idx = left.len(); match pivot_idx.cmp(&k) { Ordering::Equal => Some(pivot), Ordering::Greater => select(&left, k), Ordering::Less => select(&right, k - (pivot_idx + 1)), } }, } } fn median(data: &[i32]) -> Option<f32> { let size = data.len(); match size { even if even % 2 == 0 => { let fst_med = select(data, (even/2) - 1); let snd_med = select(data, even/2); match (fst_med, snd_med) { (Some(fst), Some(snd)) => Some((fst + snd) as f32/2.0), _ => None } }, odd => select(data, odd/2).map(|x| x as f32) } } fn main() { let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11]; let part = partition(&data); println!("Partition is {:?}", part); let sel = select(&data, 5); println!("Selection at ordered index {} is {:?}", 5, sel); let med = median(&data); println!("Median is {:?}", med); }
最后一个示例是计算常见值,使用可变的[HashMap
],从集合中,收集每个不同整数的计数,会用到一个[fold
]以及[entry
]API。[HashMap
]中,会用[max_by_key
]搞到最常见的值。
use std::collections::HashMap; fn main() { let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11]; let frequencies = data.iter().fold(HashMap::new(), |mut freqs, value| { *freqs.entry(value).or_insert(0) += 1; freqs }); let mode = frequencies .into_iter() .max_by_key(|&(_, count)| count) .map(|(value, _)| *value); println!("Mode of the data is {:?}", mode); }
标准偏差
此示例计算一组测量集合的标准偏差,和 z 分数。
标准偏差,定义为方差的平方根。此处()用 f32
的 [sqrt
]计算,方差则是,每次测量与平均值[mean
]之间差的平方的总和[sum
]。
z 分数是(单个测量值 - 数据集的[mean
] / 标准方差)。
fn mean(data: &[i32]) -> Option<f32> { let sum = data.iter().sum::<i32>() as f32; let count = data.len(); match count { positive if positive > 0 => Some(sum/count as f32), _ => None, } } fn std_deviation(data: &[i32]) -> Option<f32> { match (mean(data), data.len()) { (Some(data_mean), count) if count > 0 => { let variance = data.iter().map(|value| { let diff = data_mean - (*value as f32); diff * diff }).sum::<f32>()/count as f32; Some(variance.sqrt()) }, _ => None } } fn main() { let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11]; let data_mean = mean(&data); println!("Mean is {:?}", data_mean); let data_std_deviation = std_deviation(&data); println!("Standard deviation is {:?}", data_std_deviation); let zscore = match (data_mean, data_std_deviation) { (Some(mean), Some(std_deviation)) => { let diff = data[4] as f32 - mean; Some(diff/std_deviation) }, _ => None }; println!("Z-score of data at index 4 (with value {}) is {:?}", data[4], zscore); }
杂
大整数
可以使用BigInt
,计算超过 128 位的整数。
extern crate num; 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)); }
文本处理
食谱 | 箱子 | 类别 |
---|---|---|
收集 Unicode 字形 | [![unicode-segmentation-badge]][unicode-segmentation] | |
从电子邮件地址,提取登录信息并验证 | ||
从文本中,提取独一的# 标签列表 | ||
从文本中,提取电话号码 | ||
通过匹配多个正则表达式,筛选日志文件 | ||
将一个文本模式的所有出现项,替换为另一个模式。 | ||
为一个自定义struct ,实现FromStr trait |
正则表达式
电子邮件地址提取登录名,并验证
验证电子邮件地址的格式是否正确,并提取@
符号之前的所有内容。
#[macro_use] extern crate lazy_static; extern crate regex; use regex::Regex; fn extract_login(input: &str) -> Option<&str> { lazy_static! { static ref RE: Regex = Regex::new(r"(?x) ^(?P<login>[^@\s]+)@ ([[:word:]]+\.)* [[:word:]]+$ ").unwrap(); } RE.captures(input).and_then(|cap| { cap.name("login").map(|login| login.as_str()) }) } fn main() { assert_eq!(extract_login(r"I❤email@example.com"), Some(r"I❤email")); assert_eq!( extract_login(r"sdf+sdsfsd.as.sdsd@jhkk.d.rl"), Some(r"sdf+sdsfsd.as.sdsd") ); assert_eq!(extract_login(r"More@Than@One@at.com"), None); assert_eq!(extract_login(r"Not an email@email"), None); }
从文本中,提取单一的#
标签的列表
从文本中,提取、排序和单个化#
标签的列表。
这里给出的#
标签 正则式,只捕获以字母开头的#
标签。完整的Twitter #标签 正则式更复杂。
extern crate regex; #[macro_use] extern crate lazy_static; use regex::Regex; use std::collections::HashSet; fn extract_hashtags(text: &str) -> HashSet<&str> { lazy_static! { static ref HASHTAG_REGEX : Regex = Regex::new( r"\#[a-zA-Z][0-9a-zA-Z_]*" ).unwrap(); } HASHTAG_REGEX.find_iter(text).map(|mat| mat.as_str()).collect() } fn main() { let tweet = "Hey #world, I just got my new #dog, say hello to Till. #dog #forever #2 #_ "; let tags = extract_hashtags(tweet); assert!(tags.contains("#dog") && tags.contains("#forever") && tags.contains("#world")); assert_eq!(tags.len(), 3); }
从文本中,提取电话号码
处理字符串时使,用Regex::captures_iter
捕捉多个电话号码。这里的示例是,美国会议电话号码。
# #[macro_use] # extern crate error_chain; extern crate regex; 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 run() -> 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)? # Country Code Optional [\s\.]? (([2-9]\d{2})|\(([2-9]\d{2})\)) # Area Code [\s\.\-]? ([2-9]\d{2}) # Exchange Code [\s\.\-]? (\d{4}) # Subscriber Number"#, )?; 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(()) } # # quick_main!(run);
通过匹配多个正则表达式,筛选日志文件
读取名为application.log
的文件,并且只输出包含“version x.x.x”、或一些 IP 地址跟着端口 443(例如“192.168.0.1:443”)或特殊 warning 的那些行。
一个regex::RegexSetBuilder
组成一个regex::RegexSet
。 因为反斜杠在正则表达式中,非常常见,所以使用原始字符串文本能让它们更具可读性。
# #[macro_use] # extern crate error_chain; extern crate regex; use std::fs::File; use std::io::{BufReader, BufRead}; use regex::RegexSetBuilder; # error_chain! { # foreign_links { # Io(std::io::Error); # Regex(regex::Error); # } # } # fn run() -> 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(()) } # # quick_main!(run);
将一个文本模式的所有出现项,替换为另一个模式。
将所有出现的标准 ISO 8601 YYY-MM-DD 的日期模式,替换等效的带斜线美式英文日期。例如2013-01-15
变成01/15/2013
.
方法Regex::replace_all
会替换整个正则式的所有出现项。&str
实现了Replacer
trait,这允许类似$abcde
这样的变量,去引用在搜索的正则式中,相应的命名捕获组(?P<abcde>REGEX)
。见替换字符串语法示例,和转义的细节。
extern crate regex; #[macro_use] extern crate 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
箱子的UnicodeSegmentation::graphemes
函数,可用来,从 UTF-8 字符串中,收集单个 Unicode 字形。
#[macro_use] extern crate unicode_segmentation; 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], "é"); }
为一个自定义struct
,实现FromStr
trait
创建一个自定义结构RGB
,并实现FromStr
trait,能将提供的颜色 hex 代码,转换为 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; // 解析一个颜色 hex 代码,如 '#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 } ); }
网页编程
刮擦网页
食谱 | 箱子 | 类别 |
---|---|---|
从网页 HTML 中,提取所有链接 | ||
检查网页,是否有断开的链接 | ||
从 Mediawiki markup 中,提取所有独一链接 |
统一资源位置(URL)
食谱 | 箱子 | 类别 |
---|---|---|
将字符串的一个 URL,解析为Url 类型 | ||
通过移除路径段,创建一个 base URL | ||
从 base URL ,创建新的 URL | ||
提取 URL 源(方案名/主机/端口) | ||
从 URL 中,删除片段标识符和查询对 |
媒体类型(MIME)
食谱 | 箱子 | 类别 |
---|---|---|
从字符串,获取 MIME 类型 | ||
从文件名,获取 MIME 类型 | ||
解析 HTTP 响应的 MIME 类型 |
客户端
食谱 | 箱子 | 类别 |
---|---|---|
发出 HTTP GET 请求 | ||
查询 GitHub API | ||
检查 API 资源,是否存在 | ||
使用 GitHub API ,创建和删除 Gist | ||
使用一个(具备)分页的 RESTful API | ||
将文件下载到临时目录 | ||
使用 HTTP range 标头,进行部分下载 | ||
POST 文件,到 paste.rs |
提取链接
从网页 HTML 中,提取所有链接
使用reqwest::get
,去执行一个 HTTP GET 请求,然后使用Document::from_read
将响应解析为 HTML 文档。拿find
,配上Name
标准“a”,检索所有链接。在Selection
上,调用filter_map
留下链接中 URL,包含“href”attr
的。
# #[macro_use] # extern crate error_chain; extern crate reqwest; extern crate select; use select::document::Document; use select::predicate::Name; # # error_chain! { # foreign_links { # ReqError(reqwest::Error); # IoError(std::io::Error); # } # } fn run() -> Result<()> { let res = reqwest::get("https://www.rust-lang.org/en-US/")?; Document::from_read(res)? .find(Name("a")) .filter_map(|n| n.attr("href")) .for_each(|x| println!("{}", x)); Ok(()) } # # quick_main!(run);
检查网页是否,有断开的链接
调用get_base_url
以检索 base URL。如果文档有 base 标签,从 base 标签获取attr
。Position::BeforePath
作为 原始 URL 的 默认行为。
遍历文档中的链接,并用url::ParseOptions
和Url::parse
解析。用 reqwest
对链接做请求,并验证StatusCode
。
# #[macro_use] # extern crate error_chain; extern crate reqwest; extern crate select; extern crate url; use std::collections::HashSet; use url::{Url, Position}; use reqwest::StatusCode; use select::document::Document; use select::predicate::Name; # # error_chain! { # foreign_links { # ReqError(reqwest::Error); # IoError(std::io::Error); # UrlParseError(url::ParseError); # } # } 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) } fn check_link(url: &Url) -> Result<bool> { let res = reqwest::get(url.as_ref())?; Ok(res.status() != StatusCode::NOT_FOUND) } fn run() -> Result<()> { let url = Url::parse("https://www.rust-lang.org/en-US/")?; let res = reqwest::get(url.as_ref())?; let document = Document::from_read(res)?; let base_url = get_base_url(&url, &document)?; 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(); links .iter() .filter(|link| check_link(link).ok() == Some(false)) .for_each(|x| println!("{} is broken.", x)); Ok(()) } # # quick_main!(run);
从 Mediawiki markup 中,提取所有独一链接
使用reqwest::get
,拉取 MediaWiki 源页面,然后Regex::captures_iter
查找内部和外部链接的所有条目。使用Cow
避免过度String
分配 (单一)。
Mediawiki 链接语法,在这里有所描述。
# #[macro_use] # extern crate error_chain; #[macro_use] extern crate lazy_static; extern crate reqwest; extern crate regex; use std::io::Read; use std::collections::HashSet; use std::borrow::Cow; use regex::Regex; # error_chain! { # foreign_links { # Io(std::io::Error); # Reqwest(reqwest::Error); # Regex(regex::Error); # } # } # fn extract_links(content: &str) -> Result<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(); Ok(links) } fn run() -> Result<()> { let mut content = String::new(); reqwest::get( "https://en.wikipedia.org/w/index.php?title=Rust_(programming_language)&action=raw", )? .read_to_string(&mut content)?; println!("{:#?}", extract_links(&content)?); Ok(()) } # # quick_main!(run);
统一资源位置
将字符串的一个 URL,解析为Url
类型
这个parse
方法,它来自url
箱子,用来验证并解析一个&str
,变为一个Url
结构。输入的字符串,可能格式不正确,因此,该方法返回Result<Url, ParseError>
.
一旦解析了 URL,它就可以使用Url
类型的所有方法。
extern crate 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(()) }
通过移除路径段,创建一个 base URL
base URL 包括一个协议和一个域。base URL 没有文件夹、文件或查询字符串。这每一项都将从给定的 URL 中,剥离出来。PathSegmentsMut::clear
会删除路径,和Url::set_query
会删除查询字符串。
# #[macro_use] # extern crate error_chain; extern crate url; use url::Url; # # error_chain! { # foreign_links { # UrlParse(url::ParseError); # } # errors { # CannotBeABase # } # } fn run() -> 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) } # # quick_main!(run);
从一个 base URL ,创建新的 URL
这个join
方法,用 base 路径和相对路径,创建新的 URL。
extern crate 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 的源(协议方案名/主机/端口)
这个Url
结构,公有了各种方法,用来提取有关它所表示 URL 的信息。
extern crate 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
产生相同的结果。
# #[macro_use] # extern crate error_chain; extern crate url; use url::{Url, Origin, Host}; # error_chain! { # foreign_links { # UrlParse(url::ParseError); # } # } # fn run() -> 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(()) } # # quick_main!(run);
从 URL 中,删除片段标识符和查询对
解析Url
,并url::Position
把它切成片,删除不需要的 URL 部分。
extern crate 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箱子,从字符串中解析出一个MIME
类型。FromStrError
会在unwrap_or
作用域,生成一个默认MIME
类型。
extern crate 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箱子,从给出的文件名中,返回当前 MIME 类型。程序将检查文件扩展名,并与已知列表匹配。返回值为mime:Mime
。
extern crate mime; use mime::Mime; fn find_mimetype (filename : &String) -> Mime{ let parts : Vec<&str> = filename.split('.').collect(); let res = match parts.last() { Some(v) => match *v { "png" => mime::IMAGE_PNG, "jpg" => mime::IMAGE_JPEG, "json" => mime::APPLICATION_JSON, &_ => mime::TEXT_PLAIN, }, None => mime::TEXT_PLAIN, }; return res; } fn main() { let filenames = vec!("foobar.jpg", "foo.bar", "foobar.png"); for file in filenames { let mime = find_mimetype(&file.to_owned()); println!("MIME for {}: {}", file, mime); } }
分析 HTTP 响应的 mime 类型
从reqwest
那里,接收到一个 HTTP 响应的时候,这个MIME 类型或是媒体类型,可以在Content-Type标头中找到。reqwest::header::HeaderMap::get
将标头检索为reqwest::header::HeaderValue
,这可以转换为一个字符串。这个mime
箱子,之后就可以解析它了,并产生一个mime::Mime
值。
这个mime
箱子,还定义了一些常用的 MIME 类型。
请注意reqwest::header
模块,是从http
箱子导出的。
# #[macro_use] # extern crate error_chain; extern crate mime; extern crate reqwest; 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); # } # } fn run() -> Result<()> { let response = reqwest::get("https://www.rust-lang.org/logos/rust-logo-32x32.png")?; 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(()) } # # quick_main!(run);
客户端
食谱 | 箱子 | 类别 |
---|---|---|
发出 HTTP GET 请求 | ||
查询 GitHub API | ||
检查 API 资源,是否存在 | ||
使用 GitHub API ,创建和删除 Gist | ||
使用一个(具备)分页的 RESTful API | ||
将文件下载到临时目录 | ||
使用 HTTP range 标头,进行部分下载 | ||
POST 文件,到 paste.rs |
提出请求
发出 HTTP GET 请求
解析提供的 URL ,并使用reqwest::get
制作一个同步 HTTP 请求。 打印获得的reqwest::Response
的状态和标头。通过使用read_to_string
,将 HTTP 响应主体,读取到分配的String
。
# #[macro_use] # extern crate error_chain; extern crate reqwest; use std::io::Read; # # error_chain! { # foreign_links { # Io(std::io::Error); # HttpRequest(reqwest::Error); # } # } fn run() -> Result<()> { let mut res = reqwest::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(()) } # # quick_main!(run);
调用 Web API
查询 GitHub API
reqwest::get
查询 Github 的stargazers API v3,获取星号标记项目的所有用户的列表。reqwest::Response
用Response::json
反序列化为User
对象,而这对象实现了serde::Deserialize
。
#[macro_use] extern crate serde_derive; extern crate reqwest; use reqwest::Error; #[derive(Deserialize, Debug)] struct User { login: String, id: u32, } 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 mut response = reqwest::get(&request_url)?; let users: Vec<User> = response.json()?; println!("{:?}", users); Ok(()) }
检查一个 API 资源是否存在
使用标头请求(Client::head
),查询 GitHub 用户端点,然后检查响应代码,以确定是否成功。这是一种无需接收主体,即可快速查询 REST 资源的方法。reqwest::Client
与ClientBuilder::timeout
合作,确保请求的持续时间不会超时。
extern crate reqwest; use reqwest::Error; use std::time::Duration; use reqwest::ClientBuilder; fn main() -> Result<(), Error> { 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()?; if response.status().is_success() { println!("{} is a user!", user); } else { println!("{} is not a user!", user); } Ok(()) }
使用 GitHub API ,创建和删除 Gist
向 Github gists API v3 发出 POST 请求,创建一个 Gist ,由Client::post
完成,并使用Client::delete
DELETE 请求,将其删除。
这个reqwest::Client
负责两个请求的详细信息,包括 URL、主体和身份验证。POST 主体来自serde_json::json!
宏,它能提供任意 JSON 主体。调用RequestBuilder::json
设置请求正文。RequestBuilder::basic_auth
就处理身份验证。同步调用RequestBuilder::send
执行请求。
# #[macro_use] # extern crate error_chain; extern crate reqwest; #[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_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, } fn run() -> 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 mut response = Client::new() .post(request_url) .basic_auth(gh_user.clone(), Some(gh_pass.clone())) .json(&gist_body) .send()?; let gist: Gist = response.json()?; 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()?; println!("Gist {} deleted! Status code: {}",gist.id, response.status()); Ok(()) } # # quick_main!(run);
示例使用HTTP 基本 身份验证,这是为了获取GitHub API授权访问。典型的用例将使用一个更复杂的OAuth授权流程。
消耗一个分页的 RESTful API
在方便的 Rust 迭代器中,包装一个分页的 Web API。迭代器在到达每一页的末尾时,从远程服务器惰性地获取下一页的结果。
#[macro_use] extern crate serde_derive; extern crate reqwest; use reqwest::Error; #[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::Client, page: u32, per_page: u32, total: u32, } impl ReverseDependencies { fn of(crate_id: &str) -> Result<Self, Error> { Ok(ReverseDependencies { crate_id: crate_id.to_owned(), dependencies: vec![].into_iter(), client: reqwest::Client::new(), page: 0, per_page: 100, total: 0, }) } fn try_next(&mut self) -> Result<Option<Dependency>, Error> { 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, Error>; 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<(), Error> { for dep in ReverseDependencies::of("serde")? { println!("reverse dependency: {}", dep?.crate_id); } Ok(()) }
下载
将文件下载到临时目录
创建一个临时目录TempDir::new
,并使用reqwest::get
在 HTTP,同步下载一个文件。
创建一个目标File
,姓名来自TempDir::path
内部的Response::url
,而io::copy
就把下载的数据复制到其中。临时目录,会run
函数返回时,自动移除。
# #[macro_use] # extern crate error_chain; extern crate reqwest; extern crate tempdir; use std::io::copy; use std::fs::File; use tempdir::TempDir; # # error_chain! { # foreign_links { # Io(std::io::Error); # HttpRequest(reqwest::Error); # } # } fn run() -> Result<()> { let tmp_dir = TempDir::new("example")?; let target = "https://www.rust-lang.org/logos/rust-logo-512x512.png"; let mut response = reqwest::get(target)?; 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)? }; copy(&mut response, &mut dest)?; Ok(()) } # # quick_main!(run);
POST 一个文件,到 paste.rs
reqwest::Client
建立起到 https://paste.rs 的一个连接,遵循着reqwest::RequestBuilder
模式。调用Client::post
,加上 目标 URL 参数,RequestBuilder::body
通过读取文件,设置要发送的内容,以及RequestBuilder::send
会堵塞,直到文件上载并返回响应。read_to_string
返回响应,并显示在控制台中。
extern crate reqwest; # #[macro_use] # extern crate error_chain; # use std::fs::File; use std::io::Read; use reqwest::Client; # # error_chain! { # foreign_links { # HttpRequest(reqwest::Error); # IoError(::std::io::Error); # } # } fn run() -> Result<()> { let paste_api = "https://paste.rs"; let file = File::open("message")?; let mut response = Client::new().post(paste_api).body(file).send()?; let mut response_body = String::new(); response.read_to_string(&mut response_body)?; println!("Your paste is located at: {}", response_body); Ok(()) } # # quick_main!(run);
使用 HTTP range 标头,进行部分下载
使用reqwest::Client::head
,得到响应的内容长度。
然后,代码使用reqwest::Client::get
,在打印进度消息的同时,下载 10240 字节的内容。这个Range 标头指定块的大小和位置。
Range 标头在射频识别芯片中定义。
# #[macro_use] # extern crate error_chain; extern crate reqwest; use std::fs::File; use std::str::FromStr; use reqwest::header::{HeaderValue, CONTENT_LENGTH, RANGE}; use reqwest::StatusCode; # # 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); # // 注意(unwrap): `HeaderValue::from_str` 仅在值不是 有效 ASCII 字符,才会失败 # // 因为,这个格式字符串是静态的,两个值是整数, # // 也就是说,失败不会发生。 # Some(HeaderValue::from_str(&format!("bytes={}-{}", prev_start, self.start - 1)).unwrap()) # } # } # } fn run() -> Result<()> { let url = "https://httpbin.org/range/102400?duration=2"; const CHUNK_SIZE: u32 = 10240; let client = reqwest::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) { bail!("Unexpected server response: {}", status) } std::io::copy(&mut response, &mut output_file)?; } println!("Finished with success!"); Ok(()) } # # quick_main!(run);