Rust 烹饪书

这个Rust 烹饪书是一套简单的例子,它演示了如何使用 Rust 生态系统的箱子,作为完成常见编程任务的良好实践。

了解 Rust 烹饪书 更多,包括如何阅读这本书的提示、如何使用示例,以及约定好的注释风格。

贡献

这个项目的目的是,让新的 Rust 程序员能够容易做出贡献,帮个小忙,并且是一个与 Rust 社区接触的简单方法。本库十分欢迎帮助。详情见CONTRIBUTING.md.

算法

食谱 箱子 分类
生成,随机数 rand-badge cat-science-badge
生成,范围内的随机数 rand-badge cat-science-badge
生成,具有给定分布的随机数 rand-badge cat-science-badge
生成,自定义类型的随机值 rand-badge cat-science-badge
从一组字母字符,创建随机密码 rand-badge cat-os-badge
从一组用户定义的字符,创建随机密码 rand-badge cat-os-badge
排序一个,整数的 vector std-badge cat-science-badge
排序一个,浮点的 vector std-badge cat-science-badge
排序一个,结构的 vector std-badge cat-science-badge

命令行

食谱 箱子 分类
解析命令行参数 clap-badge cat-command-line-badge
ANSI 终端 ansi_term-badge cat-command-line-badge

压缩

食谱 箱子 分类
解压缩 一个 tarball flate2-badge tar-badge cat-compression-badge
将一个目录压缩为 tarball flate2-badge tar-badge cat-compression-badge
从路径中删除前缀时,解压缩一个 tarball flate2-badge tar-badge cat-compression-badge

并发性

食谱 箱子 类别
生成一个短命线程 crossbeam-badge cat-concurrency-badge
保持全局可变状态 lazy_static-badge cat-rust-patterns-badge
并发计算所有 *.iso 文件的 SHA1 和 threadpool-badge walkdir-badge num_cpus-badge ring-badge cat-concurrency-badgecat-filesystem-badge
将绘制分形工作,分派到线程池 threadpool-badge num-badge num_cpus-badge image-badge cat-concurrency-badgecat-science-badgecat-rendering-badge
并行,改变数组的元素 rayon-badge cat-concurrency-badge
如果集合的任何或所有元素,与给定物匹配,则并行测试 rayon-badge cat-concurrency-badge
并行,使用给定物搜索项 rayon-badge cat-concurrency-badge
并行,排序 vector rayon-badge rand-badge cat-concurrency-badge
并行,缩小地图 rayon-badge cat-concurrency-badge
并行,生成 JPG 缩略图 rayon-badge glob-badge image-badge cat-concurrency-badgecat-filesystem-badge

密码学

食谱 箱子 类别
计算文件的 SHA-256 码 ring-badge data-encoding-badge cat-cryptography-badge
使用 HMAC 码,签名并验证消息 ring-badge cat-cryptography-badge
用 PBKDF2 对密码,进行 加盐(Salt) 和 哈希 操作 ring-badge data-encoding-badge cat-cryptography-badge

数据结构

食谱 箱子 类别
定义表示为位字段的类型,并操作 bitflags-badge cat-no-std-badge

数据库

食谱 箱子 类别
创建 sqlite 数据库 rusqlite-badge cat-database-badge
插入和查询数据 rusqlite-badge cat-database-badge
在 Postgres 数据库中,创建表 [![postgres-badge]][postgres] cat-database-badge
插入和查询数据 [![postgres-badge]][postgres] cat-database-badge
综合数据 [![postgres-badge]][postgres] cat-database-badge

日期和时间

食谱 箱子 类别
测量已用时间 std-badge cat-time-badge
执行,检查日期和时间的计算 chrono-badge cat-date-and-time-badge
将本地时间,转换为其他时区 chrono-badge cat-date-and-time-badge
检查日期和时间 chrono-badge cat-date-and-time-badge
将日期转换为 Unix 时间戳,或相反 chrono-badge cat-date-and-time-badge
显示格式化的日期和时间 chrono-badge cat-date-and-time-badge
将字符串解析为 DateTime 结构 chrono-badge cat-date-and-time-badge

开发工具

调试

食谱 箱子 类别
将调试消息,记录到控制台 log-badge env_logger-badge cat-debugging-badge
将错误消息,记录到控制台 log-badge env_logger-badge cat-debugging-badge
记录到 stdout ,而不是 stderr log-badge env_logger-badge cat-debugging-badge
使用自定义记录器,记录消息 log-badge cat-debugging-badge
记录到 Unix 系统日志 log-badge syslog-badge cat-debugging-badge
启用每个模块的日志级别 log-badge env_logger-badge cat-debugging-badge
使用自定义环境变量,设置日志记录 log-badge env_logger-badge cat-debugging-badge
在日志消息中,包含时间戳 log-badge env_logger-badge chrono-badge cat-debugging-badge
将消息记录,到自定义位置 log-badge log4rs-badge cat-debugging-badge

版本控制

食谱 箱子 类别
解析,并增加版本字符串 semver-badge cat-config-badge
分析,复杂版本字符串 semver-badge cat-config-badge
检查给定版本,是否为预发布版本 semver-badge cat-config-badge
查找,满足给定范围的最新版本 semver-badge cat-config-badge
检查外部命令版本的兼容性 semver-badge cat-text-processing-badge cat-os-badge

构建时

食谱 箱子 类别
静态编译,并链接到捆绑的 C 库 cc-badge cat-development-tools-badge
编译,并链接到捆绑的 C++库 cc-badge cat-development-tools-badge
自定义设置时,编译 C 库 cc-badge cat-development-tools-badge

编码

食谱 箱子 类别
百分比编码(URL 编码)一个字符串 url-badge cat-encoding-badge
将字符串,编码为 application/x-www-form-urlencoded url-badge cat-encoding-badge
编码和解码十六进制 data-encoding-badge cat-encoding-badge
对 base64 进行编码和解码 base64-badge cat-encoding-badge
读取 csv 记录 csv-badge cat-encoding-badge
读取具有不同分隔符的 csv 记录 csv-badge cat-encoding-badge
筛选与断言匹配的 csv 记录 csv-badge cat-encoding-badge
使用 serde ,处理无效的 csv 数据 csv-badge serde-badge cat-encoding-badge
将记录序列化为 csv csv-badge cat-encoding-badge
使用 serde ,将记录序列化为 csv csv-badge serde-badge cat-encoding-badge
转换 csv 文件的一列信息 csv-badge serde-badge cat-encoding-badge
序列化和反序列化,非结构化 JSON serde-json-badge cat-encoding-badge
反序列化,一个 Toml 配置文件 toml-badge cat-encoding-badge
以小端序顺序,读取和写入整数 byteorder-badge cat-encoding-badge

文件系统

食谱 箱子 类别
从文件中,读取字符串行 std-badge cat-filesystem-badge
避免写入和读取,同一文件 same_file-badge cat-filesystem-badge
随机使用内存映射,访问文件 memmap-badge cat-filesystem-badge
过去 24 小时内,修改过的文件名 std-badge cat-filesystem-badge cat-os-badge
查找给定路径的循环 same_file-badge cat-filesystem-badge
递归查找,重复的文件名 walkdir-badge cat-filesystem-badge
递归查找,具有给定断言的所有文件 walkdir-badge cat-filesystem-badge
跳过点(隐藏)文件,遍历目录 walkdir-badge cat-filesystem-badge
在给定深度(目录),递归计算文件大小 walkdir-badge cat-filesystem-badge
递归查找,所有 PNG 文件 glob-badge cat-filesystem-badge
查找具有给定模式的所有文件,忽略文件名大小写 glob-badge cat-filesystem-badge

硬件支持

食谱 箱子 类别
检查逻辑 CPU 的核数 num_cpus-badge cat-hardware-support-badge

内存管理

食谱 箱子 类别
声明,延迟计算的常量 lazy_static-badge cat-caching-badge cat-rust-patterns-badge

网络

食谱 箱子 类别
侦听,未使用的端口 TCP/IP std-badge cat-net-badge

操作系统

食谱 箱子 类别
运行外部命令,并处理 stdout regex-badge cat-os-badge cat-text-processing-badge
运行传递到 stdin 的外部命令,并检查错误代码 regex-badge cat-os-badge cat-text-processing-badge
运行管道的外部命令 std-badge cat-os-badge
将子进程的 stdout 和 stderr ,重定向到同一文件 std-badge cat-os-badge
连续处理,子进程的输出 std-badge cat-os-badgecat-text-processing-badge

科学类

数学

食谱 箱子 类别
vector 和 ndarray-badge cat-science-badge
vector 范数 ndarray-badge cat-science-badge
矩阵相加 ndarray-badge cat-science-badge
矩阵乘法 ndarray-badge cat-science-badge
用 vector 和矩阵,相乘一个标量 ndarray-badge cat-science-badge
反转矩阵 [![nalgebra-badge]][nalgebra] cat-science-badge
计算三角形的边长 std-badge cat-science-badge
验证 tan 等于 sin 除以 cos std-badge cat-science-badge
地球两点之间的距离 std-badge cat-science-badge
创建复数 num-badge cat-science-badge
复数相加 num-badge cat-science-badge
复数的数学函数 num-badge cat-science-badge
集中趋势度量 std-badge cat-science-badge
计算标准偏差 std-badge cat-science-badge
大整数 num-badge cat-science-badge

文本处理

食谱 箱子 类别
收集 Unicode 字形 [![unicode-segmentation-badge]][unicode-segmentation] cat-encoding-badge
从电子邮件地址,提取登录信息并验证 regex-badge lazy_static-badge cat-text-processing-badge
从文本中,提取独一的#标签列表 regex-badge lazy_static-badge cat-text-processing-badge
从文本中,提取电话号码 regex-badge cat-text-processing-badge
通过匹配多个正则表达式,筛选日志文件 regex-badge cat-text-processing-badge
将一个文本模式的所有出现项,替换为另一个模式。 regex-badge lazy_static-badge cat-text-processing-badge
为一个自定义struct,实现FromStrtrait std-badge cat-text-processing-badge

网页编程

刮擦网页

食谱 箱子 类别
从网页 HTML 中,提取所有链接 reqwest-badge select-badge cat-net-badge
检查网页,是否有断开的链接 reqwest-badge select-badge url-badge cat-net-badge
从 Mediawiki markup 中,提取所有独一链接 reqwest-badge regex-badge cat-net-badge

统一资源位置(URL)

食谱 箱子 类别
将字符串的一个 URL,解析为Url类型 url-badge cat-net-badge
通过移除路径段,创建一个 base URL url-badge cat-net-badge
从 base URL ,创建新的 URL url-badge cat-net-badge
提取 URL 源(方案名/主机/端口) url-badge cat-net-badge
从 URL 中,删除片段标识符和查询对 url-badge cat-net-badge

媒体类型(MIME)

食谱 箱子 类别
从字符串,获取 MIME 类型 mime-badge cat-encoding-badge
从文件名,获取 MIME 类型 mime-badge cat-encoding-badge
解析 HTTP 响应的 MIME 类型 mime-badge reqwest-badge cat-net-badge cat-encoding-badge

客户端

食谱 箱子 类别
发出 HTTP GET 请求 reqwest-badge cat-net-badge
查询 GitHub API reqwest-badge serde-badge cat-net-badge cat-encoding-badge
检查 API 资源,是否存在 reqwest-badge cat-net-badge
使用 GitHub API ,创建和删除 Gist reqwest-badge serde-badge cat-net-badge cat-encoding-badge
使用一个(具备)分页的 RESTful API reqwest-badge serde-badge cat-net-badge cat-encoding-badge
将文件下载到临时目录 reqwest-badge tempdir-badge cat-net-badge cat-filesystem-badge
使用 HTTP range 标头,进行部分下载 reqwest-badge cat-net-badge
POST 文件,到 paste.rs reqwest-badge cat-net-badge

关于“Rust 烹饪”

目录

这本书的目标受众

本手册适用于新的 Rust 程序员,因此他们可以快速了解 Rust crate(箱子) 生态系统的能力。它也适用于经验丰富的 Rust 程序员,他们应该在食谱中,找到如何完成常见任务的简单提示。

如何阅读本书

烹饪书的索引页包含完整的食谱列表,分为几个部分:“基础”,“编码”,“并发”等。这些部分本身或多或少地按顺序排列,后面的部分,更先进,偶尔是在前面部分的概念之上构建。

在索引中,每个部分都包含一个食谱列表。食谱是要完成任务的简单陈述,例如“生成范围内的随机数”;并且每个食谱都标有徽章,表明哪个箱子是他们所使用的,像rand-badge,以及那些箱子在crates.io上,属于哪些类别。像cat-science-badge

新的 Rust 程序员,应该从第一部分开始,按顺序到最后一部分,这样阅读起来会很舒服,这样做可以让人们对 crate 生态系统,有一个很好的大菊观。单击,索引中的章节标题,或单击,侧栏以导航到某章节页面。

如果,您只是在寻找简单任务的解决方案,那么今时今日的烹饪书,还较难导航。查找特定食谱的最简单方法是扫描索引,查找感兴趣的箱子和类别。从那里,单击食谱名称进行查看。这导航方面在未来会改善的。

如何使用食谱

食谱旨在让您即时访问到,能工作的代码,并全面解释其正在执行的操作,并指导您获取更多信息。

烹饪书中的所有食谱都是完整的,自包含(自给自足)的程序,因此可以将它们直接复制到,您自己的项目中进行实验。为此,请按照以下说明,进行操作。

考虑这个例子:“生成一个范围内的随机数”:

rand-badge cat-science-badge

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!宏,自定义一个ErrorResult类型,以及来自两个标准库的错误类型的自动转换。自动转换使?操作符工作。该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密切相关,一个识别和提高此类箱子质量的项目,因此该项目在很大程度上,延迟箱子入选的问题。该过程的一部分,作为已经评估的任何箱子,都在烹饪书的(书写)范围内,而正在等待评估的箱子也是如此。

算法

食谱 箱子 分类
生成,随机数 rand-badge cat-science-badge
生成,范围内的随机数 rand-badge cat-science-badge
生成,具有给定分布的随机数 rand-badge cat-science-badge
生成,自定义类型的随机值 rand-badge cat-science-badge
从一组字母字符,创建随机密码 rand-badge cat-os-badge
从一组用户定义的字符,创建随机密码 rand-badge cat-os-badge
排序一个,整数的 vector std-badge cat-science-badge
排序一个,浮点的 vector std-badge cat-science-badge
排序一个,结构的 vector std-badge cat-science-badge

生成随机值

生成随机数

rand-badge cat-science-badge

通过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>());
}

生成范围内的随机数

rand-badge cat-science-badge

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

生成给定分布的随机数

rand-badge cat-science-badge

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

生成自定义类型的随机值

rand-badge cat-science-badge

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

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

rand-badge cat-os-badge

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

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

rand-badge cat-os-badge

使用用户自定义的字节字符串,随机生成给定长度的 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

std-badge cat-science-badge

此示例使用vec::sort对整数 vector 进行排序。替代方案是使用vec::sort_unstable,它可以更快,但不保留相等元素的顺序。

fn main() {
    let mut vec = vec![1, 5, 10, 2, 15];

    vec.sort();

    assert_eq!(vec, vec![1, 2, 5, 10, 15]);
}

排序浮点数 vector

std-badge cat-science-badge

可以使用vec::sort_byPartialOrd::partial_cmp,对 f32f64 的 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

std-badge cat-science-badge

对 Person 结构的 Vector 进行排序,通过属性nameage的自然顺序(按名称和年龄)。为了使 Person 可排序,你需要四个 traitEqPartialEqOrdPartialOrd。可以简单地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),
        ]);

}

命令行

食谱 箱子 分类
解析命令行参数 clap-badge cat-command-line-badge
ANSI 终端 ansi_term-badge cat-command-line-badge

拍手(clap)基本

解析命令行参数

clap-badge cat-command-line-badge

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

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

这个程序描述了[ansi_term箱子]的用法,以及它是如何用于控制ANSI终端上的颜色和格式,例如,蓝色粗体文本或带黄色下划线的文本。

[ansi_term]有两个主要的数据结构:ANSIStringStyle。一个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 flate2-badge tar-badge cat-compression-badge
将一个目录压缩为 tarball flate2-badge tar-badge cat-compression-badge
从路径中删除前缀时,解压缩一个 tarball flate2-badge tar-badge cat-compression-badge

使用 Tarballs

解压缩 一个 tarball

flate2-badge tar-badge cat-compression-badge

解压缩(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

flate2-badge tar-badge cat-compression-badge

/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

flate2-badge tar-badge cat-compression-badge

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

并发性

食谱 箱子 类别
生成一个短命线程 crossbeam-badge cat-concurrency-badge
保持全局可变状态 lazy_static-badge cat-rust-patterns-badge
并发计算所有 *.iso 文件的 SHA1 和 threadpool-badge walkdir-badge num_cpus-badge ring-badge cat-concurrency-badgecat-filesystem-badge
将绘制分形工作,分派到线程池 threadpool-badge num-badge num_cpus-badge image-badge cat-concurrency-badgecat-science-badgecat-rendering-badge
并行,改变数组的元素 rayon-badge cat-concurrency-badge
如果集合的任何或所有元素,与给定物匹配,则并行测试 rayon-badge cat-concurrency-badge
并行,使用给定物搜索项 rayon-badge cat-concurrency-badge
并行,排序 vector rayon-badge rand-badge cat-concurrency-badge
并行,缩小地图 rayon-badge cat-concurrency-badge
并行,生成 JPG 缩略图 rayon-badge glob-badge image-badge cat-concurrency-badgecat-filesystem-badge

线程

生成一个短命的线程

crossbeam-badge cat-concurrency-badge

该示例使用了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-badge cat-rust-patterns-badge

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

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

本示例:对当前目录中,具有 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(())
}

将绘制分形工作,分派到线程池

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

本示例:绘制[朱利亚集合]的一个分形,生成一个图像,会用到分布式计算的线程池。

通过ImageBuffer::new,为给定宽度和高度的输出图像,分配内存。Rgb::from_channels计算 RGB 像素值。创建ThreadPool线程数,等于num_cpus::get核心数。ThreadPool::execute收到的每个像素,都作为一个单独的工作。

mpsc::channel收到工作,还有Receiver::recv会检索它们。ImageBuffer::put_pixel使用数据,设置像素颜色。ImageBuffer::save就将图像写入output.png

# #[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-badge cat-concurrency-badge

该示例使用了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-badge cat-concurrency-badge

这个例子演示了如何使用rayon::anyrayon::all方法,是与std::anystd::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-badge cat-concurrency-badge

这个例子使用rayon::find_anypar_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

rayon-badge rand-badge cat-concurrency-badge

此示例:并行排序字符串 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-badge cat-concurrency-badge

这个例子使用rayon::filterrayon::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 缩略图

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

此示例:帮当前目录中的所有.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 码 ring-badge data-encoding-badge cat-cryptography-badge
使用 HMAC 码,签名并验证消息 ring-badge cat-cryptography-badge
用 PBKDF2 对密码,进行 加盐(Salt) 和 哈希 操作 ring-badge data-encoding-badge cat-cryptography-badge

哈希

计算文件的 SHA-256 码

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

将一些数据写入文件,然后获得文件的内容,再用使用digest::Contextdigest::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-badge cat-cryptography-badge

使用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-badge data-encoding-badge cat-cryptography-badge

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

数据结构

食谱 箱子 类别
定义表示为位字段的类型,并操作 bitflags-badge cat-no-std-badge

自定义

定义表示为位字段的类型,并操作

bitflags-badge cat-no-std-badge

创建,类型安全位字段的类型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 数据库 rusqlite-badge cat-database-badge
插入和查询数据 rusqlite-badge cat-database-badge
在 Postgres 数据库中,创建表 [![postgres-badge]][postgres] cat-database-badge
插入和查询数据 [![postgres-badge]][postgres] cat-database-badge
综合数据 [![postgres-badge]][postgres] cat-database-badge

数据库

创建 sqlite 数据库

rusqlite-badge cat-database-badge

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

插入并查询数据

rusqlite-badge cat-database-badge

Connection::open将打开在早期食谱中,创建的数据库cats。此食谱将数据插入到cat_colors,还有和cats表用到Connectionexecute方法。 首先,将数据插入cat_colors表。插入颜色(color)记录后,Connectionlast_insert_rowid方法,能获取最后插入(color)的id。这个id能用来,把数据插入到cats表。然后,使用prepare方法准备查询,它会返回statement结构。然后,再使用statementquery_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(())
}

使用事务

rusqlite-badge cat-database-badge

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-badge]][postgres] cat-database-badge

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

}

插入和查询数据

[![postgres-badge]][postgres] cat-database-badge

该食谱,用Connectionexecute方法,将数据插入author表。 然后,用Connectionquery方法,显示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(())

}

汇总数据

[![postgres-badge]][postgres] cat-database-badge

该食谱,列出了数据库中,现代艺术博物馆的前 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(())
}

日期和时间

食谱 箱子 类别
测量已用时间 std-badge cat-time-badge
执行,检查日期和时间的计算 chrono-badge cat-date-and-time-badge
将本地时间,转换为其他时区 chrono-badge cat-date-and-time-badge
检查日期和时间 chrono-badge cat-date-and-time-badge
将日期转换为 Unix 时间戳,或相反 chrono-badge cat-date-and-time-badge
显示格式化的日期和时间 chrono-badge cat-date-and-time-badge
将字符串解析为 DateTime 结构 chrono-badge cat-date-and-time-badge

持续时间和计算

Measure the elapsed time between two code sections

std-badge cat-time-badge

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

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

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

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

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

解析和显示

检查日期和时间

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

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 时间戳,反之亦然

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

NaiveDate::from_ymdNaiveTime::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);
}

显示格式化的日期和时间

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

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

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

解析已知的字符串表达格式RFC 2822RFC 3339和自定义格式,将其变为一个DateTime结构,可以分别使用DateTime::parse_from_rfc2822DateTime::parse_from_rfc3339DateTime::parse_from_str

可以在chrono::format::strftime找到,转义序列,让其可用于DateTime::format。 请注意DateTime::parse_from_str要求一个 DateTime 结构,必须是创建的,唯一标识的日期和时间。请使用NaiveDateNaiveTimeNaiveDateTime,分析没有时区的日期和时间。

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

开发工具

调试

食谱 箱子 类别
将调试消息,记录到控制台 log-badge env_logger-badge cat-debugging-badge
将错误消息,记录到控制台 log-badge env_logger-badge cat-debugging-badge
记录到 stdout ,而不是 stderr log-badge env_logger-badge cat-debugging-badge
使用自定义记录器,记录消息 log-badge cat-debugging-badge
记录到 Unix 系统日志 log-badge syslog-badge cat-debugging-badge
启用每个模块的日志级别 log-badge env_logger-badge cat-debugging-badge
使用自定义环境变量,设置日志记录 log-badge env_logger-badge cat-debugging-badge
在日志消息中,包含时间戳 log-badge env_logger-badge chrono-badge cat-debugging-badge
将消息记录,到自定义位置 log-badge log4rs-badge cat-debugging-badge

版本控制

食谱 箱子 类别
解析,并增加版本字符串 semver-badge cat-config-badge
分析,复杂版本字符串 semver-badge cat-config-badge
检查给定版本,是否为预发布版本 semver-badge cat-config-badge
查找,满足给定范围的最新版本 semver-badge cat-config-badge
检查外部命令版本的兼容性 semver-badge cat-text-processing-badge cat-os-badge

构建时

食谱 箱子 类别
静态编译,并链接到捆绑的 C 库 cc-badge cat-development-tools-badge
编译,并链接到捆绑的 C++库 cc-badge cat-development-tools-badge
自定义设置时,编译 C 库 cc-badge cat-development-tools-badge

调试

食谱 箱子 类别
将调试消息,记录到控制台 log-badge env_logger-badge cat-debugging-badge
将错误消息,记录到控制台 log-badge env_logger-badge cat-debugging-badge
记录到 stdout ,而不是 stderr log-badge env_logger-badge cat-debugging-badge
使用自定义记录器,记录消息 log-badge cat-debugging-badge
记录到 Unix 系统日志 log-badge syslog-badge cat-debugging-badge
启用每个模块的日志级别 log-badge env_logger-badge cat-debugging-badge
使用自定义环境变量,设置日志记录 log-badge env_logger-badge cat-debugging-badge
在日志消息中,包含时间戳 log-badge env_logger-badge chrono-badge cat-debugging-badge
将消息记录,到自定义位置 log-badge log4rs-badge cat-debugging-badge

日志消息

将调试消息,记录到控制台

log-badge env_logger-badge cat-debugging-badge

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

正确的错误处理,会异常情况,视为错误。此处,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

log-badge env_logger-badge cat-debugging-badge

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

使用自定义记录器,记录消息

log-badge cat-debugging-badge

实现一个自定义记录器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 系统日志

log-badge syslog-badge cat-debugging-badge

将消息记录到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.");
# }

配置日志记录

启用每个模块的日志级别

log-badge env_logger-badge cat-debugging-badge

创建两个模块: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::Levelwarn,而模块foo和模块foo::bar则分别为infodebug

WARN:test: [root] warn
WARN:test::foo: [foo] warn
INFO:test::foo: [foo] info
WARN:test::foo::bar: [bar] warn
INFO:test::foo::bar: [bar] info
DEBUG:test::foo::bar: [bar] debug

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

log-badge env_logger-badge cat-debugging-badge

Builder配置日志记录。

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

在日志消息中,包含时间戳

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

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

将消息记录,到自定义位置

log-badge log4rs-badge cat-debugging-badge

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

构建一个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(())
}

分析复杂的版本字符串。

semver-badge cat-config-badge

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

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

semver-badge cat-config-badge

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

查找满足给定范围的最新版本

semver-badge cat-config-badge

给定版本 &str 的一个列表,查找最新的semver::Versionsemver::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);

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

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

使用Command运行git --version,然后用Version::parse,将版本号解析为semver::VersionVersionReq::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 库

cc-badge cat-development-tools-badge

为了适应项目,需要附加 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++库

cc-badge cat-development-tools-badge

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

使用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-badge cat-encoding-badge
将字符串,编码为 application/x-www-form-urlencoded url-badge cat-encoding-badge
编码和解码十六进制 data-encoding-badge cat-encoding-badge
对 base64 进行编码和解码 base64-badge cat-encoding-badge
读取 csv 记录 csv-badge cat-encoding-badge
读取具有不同分隔符的 csv 记录 csv-badge cat-encoding-badge
筛选与断言匹配的 csv 记录 csv-badge cat-encoding-badge
使用 serde ,处理无效的 csv 数据 csv-badge serde-badge cat-encoding-badge
将记录序列化为 csv csv-badge cat-encoding-badge
使用 serde ,将记录序列化为 csv csv-badge serde-badge cat-encoding-badge
转换 csv 文件的一列信息 csv-badge serde-badge cat-encoding-badge
序列化和反序列化,非结构化 JSON serde-json-badge cat-encoding-badge
反序列化,一个 Toml 配置文件 toml-badge cat-encoding-badge
以小端序顺序,读取和写入整数 byteorder-badge cat-encoding-badge

字符集

URL 编码字符串

url-badge cat-encoding-badge

编码一个输入字符串,编码方式为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

url-badge cat-encoding-badge

将字符串,编码为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-badge cat-encoding-badge

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

将字节切片编码为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-badge cat-encoding-badge

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

csv-badge cat-encoding-badge

用一个 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 记录

csv-badge cat-encoding-badge

返回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-badge serde-badge cat-encoding-badge

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

csv-badge cat-encoding-badge

这个例子:演示了如何序列化一个 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

csv-badge serde-badge cat-encoding-badge

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

将包含颜色名称和十六进制颜色的 csv 文件,转换为具有颜色名称和 RGB 颜色的文件。利用csv箱子,读取和写入 csv 文件,以及serde对文件的一行,在字节之间进行反序列化和序列化。

csv::Reader::deserializeserde::Deserializestd::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-badge cat-encoding-badge

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

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

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 的 正确处理错误 error-chain-badge cat-rust-patterns-badge
避免在错误转换期间,丢弃错误 error-chain-badge cat-rust-patterns-badge
获取错误复杂场景的回溯 error-chain-badge cat-rust-patterns-badge

错误处理

main 中的正确处理错误

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

要处理尝试打开不存在的文件时,发生的错误。我们会用到error-chain,这是一个大量样板代码的库,用来[处理 Rust 的错误]。

foreign_links里面的Io(std::io::Error),允许从std::io::Errorerror_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-badge cat-rust-patterns-badge

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

获取复杂的错误场景的回溯

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

这个食谱,演示了如何处理复杂的错误场景,然后打印回溯。它依赖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的详细信息。

文件系统

食谱 箱子 类别
从文件中,读取字符串行 std-badge cat-filesystem-badge
避免写入和读取,同一文件 same_file-badge cat-filesystem-badge
随机使用内存映射,访问文件 memmap-badge cat-filesystem-badge
过去 24 小时内,修改过的文件名 std-badge cat-filesystem-badge cat-os-badge
查找给定路径的循环 same_file-badge cat-filesystem-badge
递归查找,重复的文件名 walkdir-badge cat-filesystem-badge
递归查找,具有给定断言的所有文件 walkdir-badge cat-filesystem-badge
跳过点(隐藏)文件,遍历目录 walkdir-badge cat-filesystem-badge
在给定深度(目录),递归计算文件大小 walkdir-badge cat-filesystem-badge
递归查找,所有 PNG 文件 glob-badge cat-filesystem-badge
查找具有给定模式的所有文件,忽略文件名大小写 glob-badge cat-filesystem-badge

读和写

从文件中,读取字符串行

std-badge cat-filesystem-badge

将一份三行的消息写入文件,然后由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-badge cat-filesystem-badge

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

使用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 小时内,修改过的文件名

std-badge cat-filesystem-badge

通过调用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-badge cat-filesystem-badge

使用same_file::is_same_file检测给定路径的循环。例如,可以通过符号链接,在 UNIX 系统上,创建一个循环:

mkdir -p /tmp/foo/bar/baz
ln -s /tmp/foo/  /tmp/foo/bar/baz/qux

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

递归查找,重复的文件名

walkdir-badge cat-filesystem-badge

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

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

递归查找所有文件,匹配给定断言

walkdir-badge cat-filesystem-badge

在当前目录中查找,在前一天内有修改的 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);

跳过点(隐藏)文件时,遍历目录

walkdir-badge cat-filesystem-badge

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

递归一定深度的目录,可以通过以下方式灵活设置: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 文件

glob-badge cat-filesystem-badge

递归查找,当前目录中的所有 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);

查找具有给定模式的所有文件,忽略文件名大小写。

glob-badge cat-filesystem-badge

/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 的核数 num_cpus-badge cat-hardware-support-badge

处理器

检查逻辑 CPU 的核数

num_cpus-badge cat-hardware-support-badge

[num_cpus::get]会显示当前计算机中,使用的逻辑 CPU 核数。

extern crate num_cpus;

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

内存管理

食谱 箱子 类别
声明,延迟计算的常量 lazy_static-badge cat-caching-badge cat-rust-patterns-badge

常量

声明,延迟计算的常量

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

声明一个延迟计算的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 std-badge cat-net-badge

服务器

侦听,未使用的端口 TCP/IP

std-badge cat-net-badge

在本例中,端口会显示在控制台。且程序会一直监听,直到一个请求发出。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 regex-badge cat-os-badge cat-text-processing-badge
运行传递到 stdin 的外部命令,并检查错误代码 regex-badge cat-os-badge cat-text-processing-badge
运行管道的外部命令 std-badge cat-os-badge
将子进程的 stdout 和 stderr ,重定向到同一文件 std-badge cat-os-badge
连续处理,子进程的输出 std-badge cat-os-badgecat-text-processing-badge

外部命令

运行外部命令,并处理 stdout

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

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 的外部命令,并检查错误代码

std-badge cat-os-badge

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

运行管道的外部命令

std-badge cat-os-badge

显示,当前工作目录中,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 ,重定向到同一文件

std-badge cat-os-badge

生成(Spawns)一个子进程,并重定向stdoutstderr到同一个文件。它与运行管道的外部命令差不多的想法,但会用process::Stdio,把输出写入到指定的文件。File::try_clone引用相同文件的stdoutstderr控制(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(())
}

连续处理,子进程的输出

std-badge cat-os-badge

运行外部命令,并处理 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 和 ndarray-badge cat-science-badge
vector 范数 ndarray-badge cat-science-badge
矩阵相加 ndarray-badge cat-science-badge
矩阵乘法 ndarray-badge cat-science-badge
用 vector 和矩阵,相乘一个标量 ndarray-badge cat-science-badge
反转矩阵 [![nalgebra-badge]][nalgebra] cat-science-badge
计算三角形的边长 std-badge cat-science-badge
验证 tan 等于 sin 除以 cos std-badge cat-science-badge
地球两点之间的距离 std-badge cat-science-badge
创建复数 num-badge cat-science-badge
复数相加 num-badge cat-science-badge
复数的数学函数 num-badge cat-science-badge
集中趋势度量 std-badge cat-science-badge
计算标准偏差 std-badge cat-science-badge
大整数 num-badge cat-science-badge

数学

食谱 箱子 类别
vector 和 ndarray-badge cat-science-badge
vector 范数 ndarray-badge cat-science-badge
矩阵相加 ndarray-badge cat-science-badge
矩阵乘法 ndarray-badge cat-science-badge
用 vector 和矩阵,相乘一个标量 ndarray-badge cat-science-badge
反转矩阵 [![nalgebra-badge]][nalgebra] cat-science-badge
计算三角形的边长 std-badge cat-science-badge
验证 tan 等于 sin 除以 cos std-badge cat-science-badge
地球两点之间的距离 std-badge cat-science-badge
创建复数 num-badge cat-science-badge
复数相加 num-badge cat-science-badge
复数的数学函数 num-badge cat-science-badge
集中趋势度量 std-badge cat-science-badge
计算标准偏差 std-badge cat-science-badge
大整数 num-badge cat-science-badge

线性代数

vector 总和

ndarray-badge

这个ndarray箱子支持多种创建数组的方法 —— 该食谱主要专注std::Vecndarray::Array的创建,通过from_vec完成。把两个数组加在一起和把两个数字加在一起,没有什么不同。在数组上,使用&符号,能在一次算术运算中,阻止消耗数组的操作。没有&的话,数组会被消耗。

在第一个示例中,数组ab在 let 语句(z = a + b)中移动:。 在第二个示例中,数组cd不会移动,而是为w创建一个新的数组。在 vector 总和(w = &c + &d)之后,更新cd,对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 范数

ndarray-badge

这个食谱演示了Array1类型,ArrayView1类型,fold方法,以及dot方法,它计算给定 vector 的l1l2范数。l2 范数的计算是两者中,比较简单的一个,因为它是一个 vector 的点乘积的平方根,如l2_norm函数所示。而 l1 范数,在l1_norm函数,由fold方法完成,它是元素绝对值的总和。(这也可以用x.mapv(f64::abs).scalar_sum()执行,但这将为mapv的结果分配一个新的数组。)

注意l1_norml2_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-badge cat-science-badge

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

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

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-badge]][nalgebra] cat-science-badge

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

三角法

计算一个三角形的边长

std-badge cat-science-badge

计算直角三角形斜边的长度,斜边的角度为 2 弧度,对边的长度为 80。

fn main() {
    let angle: f64 = 2.0;
    let side_length = 80.0;

    let hypotenuse = side_length/angle.sin();

    println!("Hypotenuse: {}", hypotenuse);
}

验证 tan 等于,sin 除以 cos

std-badge cat-science-badge

验证 tan(x)是否等于 sin(x)/cos(x),给出 x=6

fn main() {
    let x: f64 = 6.0;

    let a = x.tan();
    let b = x.sin()/x.cos();

    assert_eq!(a, b);
}

地球上,两点之间的距离

std-badge

默认情况下,rust 提供数学[浮点数方法],如三角函数、平方根、弧度和度数之间的转换函数等。

下面的示例使用半正矢公式,计算地球上两点的公里距离。这两个点用经纬度对表示。然后,to_radians把它们转换成弧度。sincospowisqrt计算中心角。最后,就可以计算距离。

fn main() {
    let earth_radius_kilometer = 6371.0_f64;
    let (paris_latitude_degrees, paris_longitude_degrees) = (48.85341_f64, -2.34880_f64);
    let (london_latitude_degrees, london_longitude_degrees) = (51.50853_f64, -0.12574_f64);

    let paris_latitude = paris_latitude_degrees.to_radians();
    let london_latitude = london_latitude_degrees.to_radians();

    let delta_latitude = (paris_latitude_degrees - london_latitude_degrees).to_radians();
    let delta_longitude = (paris_longitude_degrees - london_longitude_degrees).to_radians();

    let central_angle_inner = (delta_latitude/2.0).sin().powi(2)
        + paris_latitude.cos() * london_latitude.cos() * (delta_longitude/2.0).sin().powi(2);
    let central_angle = 2.0 * central_angle_inner.sqrt().asin();

    let distance = earth_radius_kilometer * central_angle;

    println!(
        "Distance between Paris and London on the surface of Earth is {:.1} kilometers",
        distance
    );
}

复数

创建复数

num-badge cat-science-badge

创建num::complex::Complex类型的复数。复数的实部和虚部必须是同一类型。

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

复数相加

num-badge cat-science-badge

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

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

当涉及如何与其他数学函数交互时,复数有一系列有趣的性质,尤其是类似数字的正弦家族函数。要将这些函数与复数一起使用,复数类型有一些内置函数,所有这些函数都可以找: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
}

统计

集中趋势度量

std-badge cat-science-badge

这些例子计算了包含在一个 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);
}

标准偏差

std-badge cat-science-badge

此示例计算一组测量集合的标准偏差,和 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);
}

大整数

num-badge cat-science-badge

可以使用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] cat-encoding-badge
从电子邮件地址,提取登录信息并验证 regex-badge lazy_static-badge cat-text-processing-badge
从文本中,提取独一的#标签列表 regex-badge lazy_static-badge cat-text-processing-badge
从文本中,提取电话号码 regex-badge cat-text-processing-badge
通过匹配多个正则表达式,筛选日志文件 regex-badge cat-text-processing-badge
将一个文本模式的所有出现项,替换为另一个模式。 regex-badge lazy_static-badge cat-text-processing-badge
为一个自定义struct,实现FromStrtrait std-badge cat-text-processing-badge

正则表达式

电子邮件地址提取登录名,并验证

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

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

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

从文本中,提取单一的#标签的列表

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

从文本中,提取、排序和单个化#标签的列表。

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

处理字符串时使,用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);

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

regex-badge cat-text-processing-badge

读取名为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);

将一个文本模式的所有出现项,替换为另一个模式。

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

将所有出现的标准 ISO 8601 YYY-MM-DD 的日期模式,替换等效的带斜线美式英文日期。例如2013-01-15变成01/15/2013.

方法Regex::replace_all会替换整个正则式的所有出现项。&str实现了Replacertrait,这允许类似$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-badge] cat-text-processing-badge

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,实现FromStrtrait

std-badge cat-text-processing-badge

创建一个自定义结构RGB,并实现FromStrtrait,能将提供的颜色 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 中,提取所有链接 reqwest-badge select-badge cat-net-badge
检查网页,是否有断开的链接 reqwest-badge select-badge url-badge cat-net-badge
从 Mediawiki markup 中,提取所有独一链接 reqwest-badge regex-badge cat-net-badge

统一资源位置(URL)

食谱 箱子 类别
将字符串的一个 URL,解析为Url类型 url-badge cat-net-badge
通过移除路径段,创建一个 base URL url-badge cat-net-badge
从 base URL ,创建新的 URL url-badge cat-net-badge
提取 URL 源(方案名/主机/端口) url-badge cat-net-badge
从 URL 中,删除片段标识符和查询对 url-badge cat-net-badge

媒体类型(MIME)

食谱 箱子 类别
从字符串,获取 MIME 类型 mime-badge cat-encoding-badge
从文件名,获取 MIME 类型 mime-badge cat-encoding-badge
解析 HTTP 响应的 MIME 类型 mime-badge reqwest-badge cat-net-badge cat-encoding-badge

客户端

食谱 箱子 类别
发出 HTTP GET 请求 reqwest-badge cat-net-badge
查询 GitHub API reqwest-badge serde-badge cat-net-badge cat-encoding-badge
检查 API 资源,是否存在 reqwest-badge cat-net-badge
使用 GitHub API ,创建和删除 Gist reqwest-badge serde-badge cat-net-badge cat-encoding-badge
使用一个(具备)分页的 RESTful API reqwest-badge serde-badge cat-net-badge cat-encoding-badge
将文件下载到临时目录 reqwest-badge tempdir-badge cat-net-badge cat-filesystem-badge
使用 HTTP range 标头,进行部分下载 reqwest-badge cat-net-badge
POST 文件,到 paste.rs reqwest-badge cat-net-badge

提取链接

从网页 HTML 中,提取所有链接

reqwest-badge select-badge cat-net-badge

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

检查网页是否,有断开的链接

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

调用get_base_url以检索 base URL。如果文档有 base 标签,从 base 标签获取attrPosition::BeforePath作为 原始 URL 的 默认行为。

遍历文档中的链接,并用url::ParseOptionsUrl::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-badge regex-badge cat-net-badge

使用reqwest::get,拉取 MediaWiki 源页面,然后Regex::captures_iter查找内部和外部链接的所有条目。使用Cow避免过度String分配 (单一)。

极客学院:Cow

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类型

url-badge cat-net-badge

这个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

url-badge cat-net-badge

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

url-badge cat-net-badge

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

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

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

下面的示例演示,如何用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-badge cat-encoding-badge

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

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 请求 reqwest-badge cat-net-badge
查询 GitHub API reqwest-badge serde-badge cat-net-badge cat-encoding-badge
检查 API 资源,是否存在 reqwest-badge cat-net-badge
使用 GitHub API ,创建和删除 Gist reqwest-badge serde-badge cat-net-badge cat-encoding-badge
使用一个(具备)分页的 RESTful API reqwest-badge serde-badge cat-net-badge cat-encoding-badge
将文件下载到临时目录 reqwest-badge tempdir-badge cat-net-badge cat-filesystem-badge
使用 HTTP range 标头,进行部分下载 reqwest-badge cat-net-badge
POST 文件,到 paste.rs reqwest-badge cat-net-badge

提出请求

发出 HTTP GET 请求

reqwest-badge cat-net-badge

解析提供的 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-badge serde-badge cat-net-badge cat-encoding-badge

reqwest::get查询 Github 的stargazers API v3,获取星号标记项目的所有用户的列表。reqwest::ResponseResponse::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 资源是否存在

reqwest-badge cat-net-badge

使用标头请求(Client::head),查询 GitHub 用户端点,然后检查响应代码,以确定是否成功。这是一种无需接收主体,即可快速查询 REST 资源的方法。reqwest::ClientClientBuilder::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

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

向 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

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

在方便的 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(())
}

下载

将文件下载到临时目录

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

创建一个临时目录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-badge cat-net-badge

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

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