匿名函数

创建

在前面的文档中有写过回调函数的逻辑,其实就是匿名函数。支持的写法有以下几种

  1. let add_one_v2 = |x: u32| -> u32 { x + 1 };
  2. let add_one_v3 = |x| { x + 1 };
  3. let add_one_v4 = |x| x + 1 ;

换成实际的例子就是

  1. use std::error::Error;
  2. fn main() {
  3. let test = |x| x;
  4. println!("{}", test(1));
  5. let a = String::from("1");
  6. let test2 = |x: u32| -> Result<u32, Box<dyn Error>> {
  7. let s: u32 = a.parse()?;
  8. Ok(s + x)
  9. };
  10. println!("{}", test2(3).unwrap());
  11. }

类似于 JS 中的 const abc = () => { ... } 的感觉。

闭包

在 Rust 中也存在闭包,概念跟 JS 中的类似,在匿名函数中可以感知当前 scope 的环境,并且拿到环境中的变量进行操作( 上面的例子里也有示范到 ),但是实际的表现会有不少差异。

  1. fn main() {
  2. let a = 1;
  3. let my_closure = || {
  4. println!("output a here, {}", a)
  5. };
  6. my_closure();
  7. }

除了上面的简单例子,还有其他闭包的用法,比如在某个函数返回一个匿名函数,然后在返回的匿名函数中操作闭包中的变量,这种玩法在 JS 中经常实现,但是在 Rust 中需要借助 move 关键词,因为 Rust 会在 scope 结束的时候自动释放 scope 中的内存占用,所以需要通过 move 关键词,将闭包中的变量借用到匿名函数中,比如

  1. fn main() {
  2. let b = get_closure();
  3. println!("{}", b(1));
  4. }
  5. fn get_closure() -> impl Fn(u32) -> u32 {
  6. let a = 6;
  7. move |x: u32| -> u32 {
  8. a + x
  9. }
  10. }

Move

move 的使用场景除了上面举的返回匿名函数的例子,还有一些其他场景和表现值得记录一下,比如下面的示例中,a2 在被借用的时候 ownership 转移到匿名函数中了,后面就无法再用 a2 了。

  1. fn main() {
  2. let a2 = String::from("1");
  3. let test3 = move |x: u32| -> u32 {
  4. let r: u32 = a2.parse().unwrap();
  5. x + r
  6. };
  7. println!("{}", test3(3));
  8. // can not use a2 anymore
  9. }

如果是整型这种存储在 Stack 中的数据配合 move 又是另一种情况,因为整型在被借用的时候是用 Copy 的形式,所以纵使 test3 函数执行的时候,a2 被重新赋值,在 test3 中的 a2 仍然是 1 。

注意,这一点跟 JS 的逻辑是完全不同的。

  1. fn main() {
  2. let mut a2 = 1;
  3. let test3 = move |x: u32| -> u32 {
  4. a2 + x
  5. };
  6. a2 = 3;
  7. println!("a2 = {}", a2);
  8. println!("a2 + x = {}", test3(1));
  9. }

如果不带 move 后面又有继续使用 test3 的话,a2 是不允许重新赋值的( 这个可以看 ownership 的文档,有说 Rust 会控制避免数据竞争的情况。 ),比如下面的例子就会抛异常。

  1. fn main() {
  2. let mut a2 = 1;
  3. let test3 = |x: u32| -> u32 {
  4. a2 + x
  5. };
  6. a2 = 3; // throw error: cannot assign to `a2` because it is borrowed
  7. println!("a2 = {}", a2);
  8. println!("a2 + x = {}", test3(1));
  9. }

迭代器

用法

迭代器允许开发者有序的操作数据。

  1. fn main() {
  2. let vec = vec![1, 2, 3];
  3. let iter = vec.iter();
  4. for x in iter {
  5. println!("{}", x);
  6. }
  7. }

或者通过 next 函数,返回的是 Option<T> ,比如下面的例子,前面三个返回是 Some<x> ,最后一个返回就是 None 了。执行 next 方法的时候会变更迭代器内部的状态,所以需要用 let mut

  1. fn main() {
  2. let vec = vec![1, 2, 3];
  3. let mut iter = vec.iter();
  4. println!("{:?}", iter.next());
  5. println!("{:?}", iter.next());
  6. println!("{:?}", iter.next());
  7. println!("{:?}", iter.next());
  8. }

for in 也可以改数据,通过 iter_mut 可以返回一个 mutable 的迭代器。

  1. fn main() {
  2. let mut vec = vec![1, 2, 3];
  3. let iter = vec.iter_mut();
  4. for x in iter {
  5. *x += 1
  6. }
  7. }

还可以用 into_inter 将列表的 ownership 转移到迭代器里

  1. fn main() {
  2. let vec = vec![1, 2, 3];
  3. let iter = vec.into_iter();
  4. println!("{:?}", vec); // 这里会报错,因为 vec 的 ownership 已经转移
  5. }

如果只需要读队列数据,可以用 iter() ,如果需要操作队列数据,用 iter_mut() ,如果想转移队列的 ownership,则用 into_iter()

链式调用

迭代器最常用的还是链式调用。

  1. fn main() {
  2. let vec2 = vec![1, 2, 3];
  3. let iter2 = vec2.into_iter();
  4. let r = iter2
  5. .zip(vec![1, 2, 3])
  6. .map(|(x, y)| x * y * 2)
  7. .filter(|&x| {
  8. println!("filter here");
  9. x > 2
  10. })
  11. .collect::<Vec<u64>>();
  12. println!("{:?}", r);
  13. }

迭代器的链式调用中,比如 zip map 这些方法,都只是注册一个行为,需要最后有个消费行为才会真正执行,消费行为就是比如 collectsum ,或者 for_each 等。

其中 collect 会转换为容器类型返回,sum 是累加,for_each 是逐个操作,比如打印等。

就拿上面的例子,如果把最后的 collect 逻辑移除,那么 filter here 那段日志就不会打印了。

迭代器的链式调用也可以用于之前的字符匹配例子中

  1. fn main() {
  2. let str = String::from("
  3. 13123
  4. 123123
  5. 121
  6. ");
  7. let nstr = str.lines()
  8. .filter(|&x| x.contains("123"))
  9. .collect::<Vec<&str>>()
  10. .join("\n");
  11. println!("{}", nstr);
  12. }

性能比较

迭代器的性能相比于使用循环性能综合来看是会更好的,因为 Rust 的编译器会对迭代器进行一些优化,比如对已知大小的迭代器操作,Rust 会将循环展开并将数据注册到栈中来提升访问效率。

所以完全不需要担心使用迭代器这种高级封装会影响代码性能。