From ee7f048875540d90ffe608fd1a930bc1e2d16741 Mon Sep 17 00:00:00 2001 From: lijinpeng Date: Sat, 8 Jan 2022 15:45:27 +0800 Subject: [PATCH 1/3] Modify content in closure.md --- .../advance/functional-programing/closure.md | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/book/contents/advance/functional-programing/closure.md b/book/contents/advance/functional-programing/closure.md index 99ca934c..53812319 100644 --- a/book/contents/advance/functional-programing/closure.md +++ b/book/contents/advance/functional-programing/closure.md @@ -268,7 +268,7 @@ where } ``` -上面的缓存有一个很大的问题:只支持`u32`类型的值,若我们想要缓存`String`类型,显然就行不通了,因此需要将`u32`替换成泛型`E`,该练习就留给读者自己完成,具体代码可以参考[这里](https://github.com/sunface/rust-course/blob/main/course-solutions/closure.md) +上面的缓存有一个很大的问题:只支持`u32`类型的值,若我们想要缓存`String`类型,显然就行不通了,因此需要将`u32`替换成泛型`E`,该练习就留给读者自己完成,具体代码可以参考[这里](https://github.com/sunface/rust-course/blob/main/book/solutions/closure.md) ## 捕获作用域中的值 @@ -378,6 +378,8 @@ true false ``` +如果你想强制闭包取得捕获变量的所有权,可以在参数列表前添加`move`关键字,这种用法通常用于闭包的生命周期大于捕获变量的生命周期时,例如将闭包返回或移入其他线程。 + 2. `FnMut`, 它以可变借用的方式捕获了环境中的值,因此可以修改该值: ```rust fn main() { @@ -510,7 +512,7 @@ fn exec(f: F) { ##### 三种Fn的关系 实际上,一个闭包并不仅仅实现某一种Fn特征,规则如下: - 所有的闭包都实现了`FnOnce`特征,因此任何一个闭包都至少可以被调用一次 -- 没有使用`move`的闭包实现了`FnMut`特征 +- 没有移出所捕获变量的所有权的闭包实现了`FnMut`特征 - 不需要对捕获变量进行改变的闭包实现了`Fn`特征 用一段代码来简单诠释上述规则: @@ -540,6 +542,32 @@ fn exec2(f: F) { 虽然,闭包只是对`s`进行了不可变借用,实际上,它可以适用于任何一种`Fn`特征:三个`exec`函数说明了一切。强烈建议读者亲自动手试试各种情况下使用的`Fn`特征,更有助于加深这方面的理解。 +关于第二条规则,有如下示例: + +```rust +fn main() { + let mut s = String::new(); + + let update_string = |str| -> String {s.push_str(str); s }; + + exec(update_string); +} + +fn exec<'a, F: FnMut(&'a str) -> String>(mut f: F) { + f("hello"); +} +``` + +```console +5 | let update_string = |str| -> String {s.push_str(str); s }; + | ^^^^^^^^^^^^^^^ - closure is `FnOnce` because it moves the variable `s` out of its environment + | // 闭包实现了`FnOnce`,因为它从捕获环境中移出了变量`s` + | | + | this closure implements `FnOnce`, not `FnMut` +``` + +此例中,闭包从捕获环境中移出了变量`s`的所有权,因此这个闭包仅实现了`FnOnce`,未实现`FnMut`和`Fn`。再次印证之前讲的**一个闭包实现了哪种Fn特征取决于该闭包如何使用被捕获的变量,而不是取决于闭包如何捕获它们**,跟是否使用`move`没有必然联系。 + 如果还是有疑惑?没关系,我们来看看这三个特征的简化版源码: ```rust pub trait Fn : FnMut { @@ -596,7 +624,7 @@ help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of t 嗯,编译器提示我们加一个`impl`关键字,哦,这样一说,读者可能就想起来了,`impl Trait`可以用来返回一个实现了指定特征的类型,那么这里`impl Fn(i32) -> i32`的返回值形式,说明我们要返回一个闭包类型,它实现了`Fn(i32) -> i32`特征。 -完美解决,但是,在[特征]那一章,我们提到过,`impl Trait`的返回方式有一个非常大的局限,就是你只能返回同样的类型,例如: +完美解决,但是,在[特征](../../basic/trait/trait.md)那一章,我们提到过,`impl Trait`的返回方式有一个非常大的局限,就是你只能返回同样的类型,例如: ```rust fn factory(x:i32) -> impl Fn(i32) -> i32 { From c68419ae1b41f772a399ed2f46fb1aa3a98e71d1 Mon Sep 17 00:00:00 2001 From: lijinpeng Date: Sat, 8 Jan 2022 17:10:22 +0800 Subject: [PATCH 2/3] Add performance bench: for vs iterator --- .../advance/functional-programing/iterator.md | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/book/contents/advance/functional-programing/iterator.md b/book/contents/advance/functional-programing/iterator.md index f17caeb0..9b4f4384 100644 --- a/book/contents/advance/functional-programing/iterator.md +++ b/book/contents/advance/functional-programing/iterator.md @@ -395,7 +395,81 @@ println!("{}", val); ``` ## 迭代器的性能 -@todo + +前面提到,要完成集合遍历,既可以使用for循环也可以使用迭代器,那么二者之间该怎么选择呢,性能有多大差距呢? + +理论分析不会有结果,直接测试最为靠谱: + +```rust +#![feature(test)] + +extern crate rand; +extern crate test; + +fn sum_for(x: &[f64]) -> f64 { + let mut result: f64 = 0.0; + for i in 0..x.len() { + result += x[i]; + } + result +} + +fn sum_iter(x: &[f64]) -> f64 { + x.iter().sum::() +} + +#[cfg(test)] +mod bench { + use test::Bencher; + use rand::{Rng,thread_rng}; + use super::*; + + const LEN: usize = 1024*1024; + + fn rand_array(cnt: u32) -> Vec { + let mut rng = thread_rng(); + (0..cnt).map(|_| rng.gen::()).collect() + + } + + #[bench] + fn bench_for(b: &mut Bencher) { + let samples = rand_array(LEN as u32); + b.iter(|| { + sum_for(&samples) + }) + } + + #[bench] + fn bench_iter(b: &mut Bencher) { + let samples = rand_array(LEN as u32); + b.iter(|| { + sum_iter(&samples) + }) + } +} +``` + +上面的代码对比了for循环和迭代器iterator完成同样的求和任务的性能对比,可以看到迭代器还要更快一点。 + +```console +test bench::bench_for ... bench: 998,331 ns/iter (+/- 36,250) +test bench::bench_iter ... bench: 983,858 ns/iter (+/- 44,673) +``` + +迭代器是 Rust 的 **零成本抽象**(zero-cost abstractions)之一,意味着抽象并不会引入运行时开销,这与 `Bjarne Stroustrup`(C++ 的设计和实现者)在 `Foundations of C++(2012)` 中所定义的 **零开销**(zero-overhead)如出一辙: + +``` +In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. +And further: What you do use, you couldn’t hand code any better. + +一般来说,C++的实现遵循零开销原则:没有使用时,你不必为其买单。 +更进一步说,需要使用时,你也无法写出更优的代码了。 +``` + +总之,迭代器是 Rust 受函数式语言启发而提供的高级语言特性,可以写出更加简洁、逻辑清晰的代码。编译器还可以通过循环展开(Unrolling)、向量化、消除边界检查等优化手段,使得迭代器和for循环都有极为高效的执行效率。 + +所以请放心大胆的使用迭代器,在获得更高的表达力的同时,也不会导致运行时的损失,何乐而不为呢! ## 学习其它方法 迭代器用的好不好,就在于你是否掌握了它的常用方法,且能活学活用,因此多多看看[标准库](https://doc.rust-lang.org/std/iter/trait.Iterator.html)是有好处的,只有知道有什么方法,在需要的时候你才能知道该用什么,就和算法学习一样。 From 99c42f75abaab4495968e9a7636f2d46e728281e Mon Sep 17 00:00:00 2001 From: lijinpeng Date: Sat, 8 Jan 2022 17:20:10 +0800 Subject: [PATCH 3/3] Update iterator.md --- book/contents/advance/functional-programing/iterator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/contents/advance/functional-programing/iterator.md b/book/contents/advance/functional-programing/iterator.md index 9b4f4384..bb9e7dfd 100644 --- a/book/contents/advance/functional-programing/iterator.md +++ b/book/contents/advance/functional-programing/iterator.md @@ -429,7 +429,6 @@ mod bench { fn rand_array(cnt: u32) -> Vec { let mut rng = thread_rng(); (0..cnt).map(|_| rng.gen::()).collect() - } #[bench] @@ -465,6 +464,7 @@ And further: What you do use, you couldn’t hand code any better. 一般来说,C++的实现遵循零开销原则:没有使用时,你不必为其买单。 更进一步说,需要使用时,你也无法写出更优的代码了。 +(翻译一下:用就完事了) ``` 总之,迭代器是 Rust 受函数式语言启发而提供的高级语言特性,可以写出更加简洁、逻辑清晰的代码。编译器还可以通过循环展开(Unrolling)、向量化、消除边界检查等优化手段,使得迭代器和for循环都有极为高效的执行效率。