diff --git a/contents/advance/converse/enum-int.md b/contents/advance/converse/enum-int.md index 68cd4938..bb1c45c0 100644 --- a/contents/advance/converse/enum-int.md +++ b/contents/advance/converse/enum-int.md @@ -1,15 +1,15 @@ # 整数转换为枚举 -在Rust中,从枚举到整数的转换很容易,但是反过来,就没那么容易,甚至部分实现还挺邪恶, 例如使用`transmute`。 +在 Rust 中,从枚举到整数的转换很容易,但是反过来,就没那么容易,甚至部分实现还挺邪恶, 例如使用`transmute`。 ## 一个真实场景的需求 -在实际场景中,从枚举到整数的转换有时还是非常需要的,例如你有一个枚举类型,然后需要从外面穿入一个整数,用于控制后续的流程走向,此时就需要用整数去匹配相应的枚举(你也可以用整数匹配整数-, -,看看会不会被喷)。 +在实际场景中,从枚举到整数的转换有时还是非常需要的,例如你有一个枚举类型,然后需要从外面传入一个整数,用于控制后续的流程走向,此时就需要用整数去匹配相应的枚举(你也可以用整数匹配整数-, -,看看会不会被喷)。 既然有了需求,剩下的就是看看该如何实现,这篇文章的水远比你想象的要深,且看八仙过海各显神通。 ## C语言的实现 -对于C语言来说,万物皆邪恶,因此我们不讨论安全,只看实现,不得不说很简洁: +对于 C 语言来说,万物皆邪恶,因此我们不讨论安全,只看实现,不得不说很简洁: ```C #include @@ -32,7 +32,7 @@ int main(void) } ``` -但是在Rust中,以下代码: +但是在 Rust 中,以下代码: ```rust enum MyEnum { A = 1, @@ -57,7 +57,7 @@ fn main() { 就会报错: `MyEnum::A => {} mismatched types, expected i32, found enum MyEnum`。 ## 使用三方库 -首先可以想到的肯定是三方库,毕竟Rust的生态目前已经发展的很不错,类似的需求总是有的,这里我们先使用`num-traits`和`num-derive`来试试。 +首先可以想到的肯定是三方库,毕竟 Rust 的生态目前已经发展的很不错,类似的需求总是有的,这里我们先使用`num-traits`和`num-derive`来试试。 在`Cargo.toml`中引入: ```toml @@ -93,7 +93,7 @@ fn main() { 除了上面的库,还可以使用一个较新的库: [`num_enums`](https://github.com/illicitonion/num_enum)。 ## TryFrom + 宏 -在Rust1.34后,可以实现`TryFrom`特征来做转换: +在 Rust 1.34 后,可以实现`TryFrom`特征来做转换: ```rust use std::convert::TryFrom; @@ -180,7 +180,7 @@ enum MyEnum { fn main() { let x = MyEnum::C; let y = x as i32; - let z: MyEnum = unsafe { ::std::mem::transmute(y) }; + let z: MyEnum = unsafe { std::mem::transmute(y) }; // match the enum that came from an int match z { @@ -191,10 +191,10 @@ fn main() { } ``` -既然是邪恶之王,当然得有真本事,无需标准库、也无需unstable的Rust版本,我们就完成了转换!awesome!?? +既然是邪恶之王,当然得有真本事,无需标准库、也无需 unstable 的 Rust 版本,我们就完成了转换!awesome!?? ## 总结 -本文列举了常用(其实差不多也是全部了,还有一个unstable特性没提到)的从整数转换为枚举的方式,推荐度按照出现的先后顺序递减。 +本文列举了常用(其实差不多也是全部了,还有一个 unstable 特性没提到)的从整数转换为枚举的方式,推荐度按照出现的先后顺序递减。 -但是推荐度最低,不代表它就没有出场的机会,只要使用边界清晰,一样可以大方光彩,例如最后的`transmute`函数. \ No newline at end of file +但是推荐度最低,不代表它就没有出场的机会,只要使用边界清晰,一样可以大放光彩,例如最后的`transmute`函数. \ No newline at end of file diff --git a/contents/advance/converse/intro.md b/contents/advance/converse/intro.md index cf5fa148..649216d6 100644 --- a/contents/advance/converse/intro.md +++ b/contents/advance/converse/intro.md @@ -1,2 +1,2 @@ # 进阶类型转换 -Rust是强类型语言,同时也是强安全语言,因此这些决定了Rust的类型转换注定比一般语言要更困难,再加上Rust的繁多的类型和类型转换特征,用于很难对这块内容了若指掌,因此我们专门整了一个专题来讨论Rust中那些不太容易的类型转换, 容易的请看[这一章](../basic/converse.md). \ No newline at end of file +Rust 是强类型语言,同时也是强安全语言,这些特性导致了 Rust 的类型转换注定比一般语言要更困难,再加上 Rust 的繁多的类型和类型转换特征,因此大家很难对这块内容了如指掌,为此我们专门整了一个专题来讨论 Rust 中那些不太容易的类型转换, 容易的请看[这一章](../basic/converse.md). \ No newline at end of file diff --git a/contents/advance/macro.md b/contents/advance/macro.md index 24446725..ff4020ce 100644 --- a/contents/advance/macro.md +++ b/contents/advance/macro.md @@ -16,13 +16,13 @@ fn main() { 虽然三种使用形式皆可,但是 Rust 内置的宏都有自己约定俗成的使用方式,例如 `vec![...]`、`assert_eq!(...)` 等。 -在 Rust 中宏分为两大类:声明式宏 `macro_rules!` 和三种过程宏( *procedural macros* ): +在 Rust 中宏分为两大类:**声明式宏( *declarative macros* )** `macro_rules!` 和三种**过程宏( *procedural macros* )**: - `#[derive]`,在之前多次见到的派生宏,可以为目标结构体或枚举派生指定的代码,例如 `Debug` 特征 - 类属性宏(Attribute-like macro),用于为目标添加自定义的属性 - 类函数宏(Function-like macro),看上去就像是函数调用 -如果感觉难以理解,也不必担心,接下来我们将逐个看看它们的庐山真面目,在次之前,先来看下为何需要宏,特别是 Rust 的函数明明已经很强大了。 +如果感觉难以理解,也不必担心,接下来我们将逐个看看它们的庐山真面目,在此之前,先来看下为何需要宏,特别是 Rust 的函数明明已经很强大了。 ## 宏和函数的区别 宏和函数的区别并不少,而且对于宏擅长的领域,函数其实是有些无能为力的。 @@ -30,7 +30,7 @@ fn main() { #### 元编程 从根本上来说,宏是通过一种代码来生成另一种代码,如果大家熟悉元编程,就会发现两者的共同点。 -在[附录 D 中](https://course.rs/appendix/derive.html)讲到的 `derive` 属性,就会自动为结构体派生出相应特征所需的代码,例如 `#[derive(Debug)]`,还有熟悉的 `println!` 和 `vec!`,所有的这些宏都会展开成相应的代码,且很可能是长得多的代码。 +在[附录 D](https://course.rs/appendix/derive.html)中讲到的 `derive` 属性,就会自动为结构体派生出相应特征所需的代码,例如 `#[derive(Debug)]`,还有熟悉的 `println!` 和 `vec!`,所有的这些宏都会展开成相应的代码,且很可能是长得多的代码。 总之,元编程可以帮我们减少所需编写的代码,也可以一定程度上减少维护的成本,虽然函数复用也有类似的作用,但是宏依然拥有自己独特的优势。 @@ -113,7 +113,7 @@ macro_rules! vec { 1. `$()` 中包含的是模式 `$x:expr`,该模式中的 `expr` 表示会匹配任何 Rust 表达式,并给予该模式一个名称 `$x` 2. 因此 `$x` 模式可以跟整数 `1` 进行匹配,也可以跟字符串 "hello" 进行匹配: `vec!["hello", "world"]` 3. `$()` 之后的逗号,意味着`1` 和 `2` 之间可以使用逗号进行分割,也意味着 `3` 既可以没有逗号,也可以有逗号:`vec![1, 2, 3,]` -4. `*` 说明之前的模式可以出现一次也可以任意次,这里出现了三次 +4. `*` 说明之前的模式可以出现零次也可以任意次,这里出现了三次 接下来,我们再来看看与模式相关联、在 `=>` 之后的代码: ```rust @@ -158,7 +158,7 @@ let v = { 由于绝大多数 Rust 开发者都是宏的用户而不是编写者,因此在这里我们不会对 `macro_rules` 进行更深入的学习,如果大家感兴趣,可以看看这本书 [ “The Little Book of Rust Macros”](https://veykril.github.io/tlborm/)。 ## 用过程宏为属性标记生成代码 -第二种常用的宏就是[*过程宏*]( *procedural macros* ),从形式上来看,过程宏跟函数较为相像,但过程宏是使用源代码作为输入参数,基于代码进行一系列操作后,再输出一段全新的代码。**注意,过程宏输出的代码并不会替换之前的代码,这一点与声明宏有很大的不同!** +第二种常用的宏就是[*过程宏*](https://doc.rust-lang.org/reference/procedural-macros.html) ( *procedural macros* ),从形式上来看,过程宏跟函数较为相像,但过程宏是使用源代码作为输入参数,基于代码进行一系列操作后,再输出一段全新的代码。**注意,过程宏输出的代码并不会替换之前的代码,这一点与声明宏有很大的不同!** 至于前文提到的过程宏的三种类型(自定义 `derive`、属性宏、函数宏),它们的工作方式都是类似的。 @@ -282,7 +282,7 @@ hello_macro 由于过程宏所在的包跟我们的项目紧密相连,因此将它放在项目之中。现在,问题又来了,该如何在项目的 `src/main.rs` 中引用 `hello_macro_derive` 包的内容? -方法有两种,第一种是将 `hello_macro_derive` 发布到 `crates.io` 或 `github` 中,就像我们引用的其它依赖一样;另一种就是使用相对路径引入的本地化方式,修改 `hello_macro/Cargo.tom` 文件添加以下内容: +方法有两种,第一种是将 `hello_macro_derive` 发布到 `crates.io` 或 `github` 中,就像我们引用的其它依赖一样;另一种就是使用相对路径引入的本地化方式,修改 `hello_macro/Cargo.toml` 文件添加以下内容: ```toml [dependencies] hello_macro_derive = { path = "../hello_macro/hello_macro_derive" } @@ -334,9 +334,9 @@ pub fn hello_macro_derive(input: TokenStream) -> TokenStream { 由于我们为 `hello_macro_derive` 函数标记了 `#[proc_macro_derive(HelloMacro)]`,当用户使用 `#[derive(HelloMacro)]` 标记了他的类型后,`hello_macro_derive` 函数就将被调用。这里的秘诀就是特征名 `HelloMacro`,它就像一座桥梁,将用户的类型和过程宏联系在一起。 -`sync` 将字符串形式的 Rust 代码解析为一个 AST 树的数据结构,该数据结构可以在随后的 `impl_hello_macro` 函数中进行操作。最后,操作的结果又会被 `quote` 包转换回 Rust 代码。这些包非常关键,可以帮我们节省大量的精力,否则你需要自己去编写支持代码解析和还原的解析器,这可不是一件简单的任务! +`syn` 将字符串形式的 Rust 代码解析为一个 AST 树的数据结构,该数据结构可以在随后的 `impl_hello_macro` 函数中进行操作。最后,操作的结果又会被 `quote` 包转换回 Rust 代码。这些包非常关键,可以帮我们节省大量的精力,否则你需要自己去编写支持代码解析和还原的解析器,这可不是一件简单的任务! -`sync.parse` 调用会返回一个 `DeriveInput` 结构体来代表解析后的 Rust 代码: +`syn::parse` 调用会返回一个 `DeriveInput` 结构体来代表解析后的 Rust 代码: ```rust DeriveInput { // --snip-- @@ -362,7 +362,7 @@ DeriveInput { - `fields: Unit` 说明源代码是一个单元结构体 - `ident: "Sunfei"` 说明类型名称为 `Sunfei`, `ident` 是标识符 `identifier` 的简写 -如果想要了解更多的信息,可以查看 [`sync` 文档](https://docs.rs/syn/1.0/syn/struct.DeriveInput.html)。 +如果想要了解更多的信息,可以查看 [`syn` 文档](https://docs.rs/syn/1.0/syn/struct.DeriveInput.html)。 大家可能会注意到在 `hello_macro_derive` 函数中有 `unwrap` 的调用,也许会以为这是为了演示目的,没有做错误处理,实际上并不是的。由于该函数只能返回 `TokenStream` 而不是 `Result`,那么在报错时直接 `panic` 来抛出错误就成了相当好的选择。当然,这里实际上还是做了简化,在生产项目中,你应该通过 `panic!` 或 `expect` 抛出更具体的报错信息。 @@ -389,7 +389,7 @@ fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream { 特征的 `hell_macro()` 函数只有一个功能,就是使用 `println!` 打印一行欢迎语句。 -其中 `stringify!` 是 Rust 提供的内置宏,可以将一个表达式(例如 `1 + 2`)在编译期转换成一个字符串字面值(`"1 + 2"`),该字面量会直接打包进编译出的二进制文件中,具有 `'static` 生命周期。而 `format!` 宏都对表达式进行求值,最终结果是一个 `String` 类型。在这里使用 `stringify!` 有两个好处: +其中 `stringify!` 是 Rust 提供的内置宏,可以将一个表达式(例如 `1 + 2`)在编译期转换成一个字符串字面值(`"1 + 2"`),该字面量会直接打包进编译出的二进制文件中,具有 `'static` 生命周期。而 `format!` 宏会对表达式进行求值,最终结果是一个 `String` 类型。在这里使用 `stringify!` 有两个好处: - `#name` 可能是一个表达式,我们需要它的字面值形式 - 可以减少一次 `String` 带来的内存分配 diff --git a/contents/test/benchmark.md b/contents/test/benchmark.md index 69276b92..bf19a3f5 100644 --- a/contents/test/benchmark.md +++ b/contents/test/benchmark.md @@ -142,14 +142,14 @@ mod tests { 其实,原因藏在`LLVM`中: `LLVM`认为`fibonacci_u64`函数调用的结果没有使用,同时也认为该函数没有任何副作用(造成其它的影响,例如修改外部变量、访问网络等), 因此它有理由把这个函数调用优化掉! -解决很简单,使用Rust标准库中的 `black_box` 函数: +解决很简单,使用 Rust 标准库中的 `black_box` 函数: ```rust for i in 100..200 { test::black_box(fibonacci_u64(test::black_box(i))); } ``` -通过这个函数,我们告诉编译器,让它尽量少做优化,此时LLVM就不会再自作主张了:) +通过这个函数,我们告诉编译器,让它尽量少做优化,此时 LLVM 就不会再自作主张了:) ```shell $ cargo bench @@ -203,11 +203,18 @@ criterion_main!(benches); 最后,使用 `cargo bench` 运行并观察结果: ```shell - Running target/release/deps/example-423eedc43b2b3a93 + Running target/release/deps/example-423eedc43b2b3a93 +Benchmarking fib 20 +Benchmarking fib 20: Warming up for 3.0000 s +Benchmarking fib 20: Collecting 100 samples in estimated 5.0658 s (188100 iterations) +Benchmarking fib 20: Analyzing fib 20 time: [26.029 us 26.251 us 26.505 us] Found 11 outliers among 99 measurements (11.11%) 6 (6.06%) high mild 5 (5.05%) high severe +slope [26.029 us 26.505 us] R^2 [0.8745662 0.8728027] +mean [26.106 us 26.561 us] std. dev. [808.98 ns 1.4722 us] +median [25.733 us 25.988 us] med. abs. dev. [234.09 ns 544.07 ns] ``` 可以看出,这个结果是明显比官方的更详尽的,如果大家希望更深入的学习它的使用,可以参见[官方文档](https://bheisler.github.io/criterion.rs/book/getting_started.html)。 diff --git a/contents/test/ci.md b/contents/test/ci.md index a6098d07..c0127977 100644 --- a/contents/test/ci.md +++ b/contents/test/ci.md @@ -1,5 +1,5 @@ # 用Github Actions进行持续集成 -[Github Actions](https://github.com/features/actions) 是官方于 2018年 推出的持续集成服务,它非常强大,本文将手把手带领大家学习如何使用 `Github Actions` 对 Rust 项目进行持续集成, +[Github Actions](https://github.com/features/actions) 是官方于 2018 年推出的持续集成服务,它非常强大,本文将手把手带领大家学习如何使用 `Github Actions` 对 Rust 项目进行持续集成。 持续集成是软件开发中异常重要的一环,大家应该都听说过 `Jenkins`,它就是一个拥有悠久历史的持续集成工具。简单来说,持续集成会定期拉取同一个项目中所有成员的相关代码,对其进行自动化构建。 @@ -15,7 +15,7 @@ 若你需要某个 `action`,不必自己写复杂的脚本,直接引用他人写好的 `action` 即可,整个持续集成过程,就变成了多个 `action` 的组合,这就是` GitHub Actions` 最厉害的地方。 #### action 的分享与引用 -既然 `action` 这么强大,我们就将自己的 `action` 分享给他人,也可以引用他人分享的 `action`,有以下几种方式: +既然 `action` 这么强大,我们就可以将自己的 `action` 分享给他人,也可以引用他人分享的 `action`,有以下几种方式: 1. 将你的 `action` 放在 github 上的公共仓库中,这样其它开发者就可以引用,例如 [github-profile-summary-cards](https://github.com/vn7n24fzkq/github-profile-summary-cards) 就提供了相应的 `action`,可以生成 github 用户统计信息,然后嵌入到你的个人主页中,具体效果[见这里](https://github.com/sunface) 2. Github 提供了一个[官方市场](https://github.com/marketplace?type=actions),里面收集了许多质量不错的 `actions`,并支持在线搜索 @@ -198,8 +198,8 @@ jobs: steps: - name: - env: - NODE_ENV: prod + env: + NODE_ENV: prod ``` 如果有多个 `env` 存在,会使用就近那个。 diff --git a/contents/test/write-tests.md b/contents/test/write-tests.md index ebebf525..20663208 100644 --- a/contents/test/write-tests.md +++ b/contents/test/write-tests.md @@ -1,6 +1,6 @@ # 编写测试及控制执行 -在 Rust 中,测试是通过函数的方式实现的,它可以用于验证非测试代码的正确性。测试函数往往依次执行以下三种行为: +在 Rust 中,测试是通过函数的方式实现的,它可以用于验证被测试代码的正确性。测试函数往往依次执行以下三种行为: 1. 设置所需的数据或状态 2. 运行想要测试的代码 @@ -62,7 +62,7 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini - 测试用例是分批执行的,`running 1 test` 表示下面的输出 `test result` 来自一个测试用例的运行结果。 - `test tests::it_works` 中包含了测试用例的名称 - `test result: ok` 中的 `ok` 表示测试成功通过 -- `1 passed` 代表成功通过一个测试用例(因为只有一个),`0 failed` : 没有测试用例失败,`0 ignored` 说明我们没有将任何测试函数标记为运行时可忽略,`0 filtered` 意味着没有对测试结果做任何过滤,`0 mesasured` 代表[性能测试(benchmark)](https://course.rs/test/benchmark.html)的结果 +- `1 passed` 代表成功通过一个测试用例(因为只有一个),`0 failed` : 没有测试用例失败,`0 ignored` 说明我们没有将任何测试函数标记为运行时可忽略,`0 filtered` 意味着没有对测试结果做任何过滤,`0 mesasured` 代表[基准测试(benchmark)](https://course.rs/test/benchmark.html)的结果 关于 `filtered` 和 `ignored` 的使用,在本章节的后续内容我们会讲到,这里暂且略过。 @@ -87,7 +87,7 @@ mod tests { } ``` -新的测试函数 `anothre` 相当简单粗暴,直接使用 `panic` 来报错,使用 `cargo test` 运行看看结果: +新的测试函数 `another` 相当简单粗暴,直接使用 `panic` 来报错,使用 `cargo test` 运行看看结果: ```shell running 2 tests test tests::another ... FAILED