Back
Featured image of post Rust 学习笔记07 - 闭包

Rust 学习笔记07 - 闭包

导航页

闭包

我们可以使用闭包(closures)创建行为的抽象:

use std::thread;
use std::time::Duration;

fn generate_workout(intensity: u32, random_number: u32) {
  let expensive_closure: fn(u32) -> u32 = |num| {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    num
  };

  if intensity < 25 {
    println!("Today, do {} pushups!", expensive_closure(intensity));
    println!("Next, do {} situps!", expensive_closure(intensity));
  } else {
    if random_number == 3 {
      println!("Take a break today! Remember to stay hydrated!");
    } else {
      println!("Today, run for {} minutes!", expensive_closure(intensity));
    }
  }
}

fn main() {
  generate_workout(7, 10)
}

闭包的语法如下:

|变量1, 变量2, ...| {
  // 中间的语句
  // 最后一行 为返回值
}

若只有一行语句/返回值,大括号可以省略:

|num| num * num * num

函数类型这样书写:

fn(输入1, 输入2, ...) -> 返回值

也可以在闭包内标注:

let sum = |x: i32, y: i32| -> i32 {
    x + y
}

高阶函数,配合泛型:

use std::fmt::Display;
use std::thread;
use std::time::Duration;

fn main() {
  let creation = || 1000 * 123012;
  let x = create_and_print(creation);
  let y = create_and_print(creation);
  println!("{x}, {y}");
}

fn create_and_print<F, T>(creation: F) -> T
where
  T: Display,
  F: Fn() -> T,
{
  let created = creation();
  println!("{}", created);
  created
}

在结构体中也可以使用高阶函数,下面是一个简单的 Lazy 实现:

struct Image<'img> {
  content: Vec<u8>,
}

impl<'img> Image<'img> {
  fn new(name: &'img str, content: Vec<u8>) -> Image<'img> {
    Image { content }
  }
}

struct Lazy<'lazy, F, T>
where
  F: Fn() -> T,
{
  calculation: F,
  value: Option<&'lazy T>,
}

impl<'lazy, F, T> Lazy<'lazy, F, T>
where
  F: Fn() -> T,
{
  fn new(calculation: F) -> Lazy<F, T> {
    Lazy {
      calculation,
      value: None,
    }
  }

  fn value(&mut self) -> &T {
    match self.value {
      Some(v) => v,
      None => {
        let v = (self.calculation)();
        self.value = Some(v);
        self.value.unwrap()
      }
    }
  }
}

fn main() {
  let mut lazy = Lazy::new(|| Image {
    content: vec![123, 123, 123, 123, 123, 123, 123, 123, 123, 123],
  });
  lazy.value();
}

闭包捕获

闭包可以通过多种方式捕获作用域中的值(也就是环境),所以函数具有几个类型:

  • FnOnce:为了消费捕获到的变量,必须获得其所有权,并在定义时移动。Once 意味着不能多次获取相同变量的所有权,所以只能被调用一次。
  • FnMut:获取可变的借用,可以改变环境
  • Fn:获取不可变的借用值

由于所有闭包都可以至少被调用一次,所以所有闭包都实现了 FnOnce。若是没有捕获变量所有权的闭包,同样也实现了 FnMut。而没有对变量进行可变访问的包,也实现了 Fn

如果希望闭包强制获得变量所有权,可以使用 move 关键字:

let x = vec![1, 2, 3];
let equal_to_x = move |z: Vec<i32>| z == x;
// println!("can't use x here: {:?}", x);
let y = vec![1, 2, 3];
assert!(equal_to_x(y));

上述例子因为闭包取得了 x 的所有权,所以在闭包定义后不可再访问。

大部分时候,当 trait 需要一个闭包时,可以从 Fn 开始,后面根据需求,可以改为 FnMutFnOnce

迭代器

迭代器允许我们编写函数式风格的代码。

在 Rust 中,迭代器是惰性的。只有调用 collect() for_each()终端操作符后,所有计算才会真正开始。

迭代器的定义如下:

pub trait Iterator {
  type Item;
  fn next(&mut self) -> Option<Self::Item>;
  // ...
}

只使用基本方法只能是枯燥无味的:

fn main() {
  let v1 = vec![1, 2, 3];

  let mut v1_iter = v1.iter();

  assert_eq!(v1_iter.next(), Some(&1));
  assert_eq!(v1_iter.next(), Some(&2));
  assert_eq!(v1_iter.next(), Some(&3));
  assert_eq!(v1_iter.next(), None);
}

但有一系列链式方法供我们调用。Rust 称其为消费适配器(consuming adaptors)

fn iterator_sum() {
  let v1 = vec![1, 2, 3];
  let v1_iter = v1.iter();
  let total: i32 = v1_iter.sum();
  // 此处不可再使用 v1_iter
  assert_eq!(total, 6);
}

每个链式方法都会获取接收者的所有权。所以在调用 sum() 后不可再次使用 v1_iter

常用方法

如果你使用过一些具有函数式风格的语言,如 Java 8 新增的 Stream,Kotlin 的一系列集合方法,或者 Scala、Clojure、Haskell 等更加纯函数式的语言。你对这些方法应该不陌生。

这里介绍几种最常见的。

foreach

这是一个终端操作符,用来遍历所有元素。

fn main() {
  let y = vec![1, 2, 3];
  y.iter().for_each(|x| println!("{x}"));
}

filter

过滤元素。

fn main() {
  let vec1 = vec![-100, 1, 2, 3, 4, 5, 6, 7, 8];
  vec1
    .iter()
    .filter(|&&i| i >= 10)
    .for_each(|i| println!("{i}"));
}

请注意,这里的 i 是引用的引用。Vector 是第一层,而迭代器是第二层。所以有两层引用。(如果 Vector 中存放的是字符串字面量,甚至会有 3 层引用……)

在闭包中使用 &&i 表示在声明时解引用。和 filter(|i| **i >= 10) 实则是一样的

map

将元素映射。

fn main() {
  let vec1 = vec!["asdf", "asdfasdf", "🤣"];
  vec1
    .iter()
    .map(|&i| i.len())
    .for_each(|i| println!("{i}"));
}

enumerate

将元素映射为 (索引, 内容) 的元组。

fn main() {
  let vec1 = vec!["asdf", "asdfasdf", "🤣"];
  vec1
    .iter()
    .enumerate()
    .for_each(|(idx, i)| println!("The content of element[{idx}]: {i}"))
}

其他的方法,可以自行查看标准库中的文档。

自定义迭代器

我们可以为结构体实现 Iterator trait 来自定义迭代器。

如这样:

impl Counter {
  fn new() -> Counter {
    Counter { count: 0 }
  }
}

impl Iterator for Counter {
  type Item = u32;

  fn next(&mut self) -> Option<Self::Item> {
    if self.count < 5 {
      self.count += 1;
      Some(self.count)
    } else {
      None
    }
  }
}

自定义的迭代器仍然可以享受一系列便捷方法:

fn main() {
  let sum: u32 = Counter::new()
    .zip(Counter::new().skip(1))
    .map(|(a, b)| a * b)
    .filter(|x| x % 3 == 0)
    .sum();
  assert_eq!(18, sum);
}

零成本抽象

通过函数内联等编译时优化。可以避免创建闭包对象。将迭代器转换成普通的语句流。

在《Rust 程序设计语言》中,作者对循环和迭代器进行了测试。结果表明迭代器并不比循环慢。甚至还要稍微快一点。

以上,就是关于闭包的基本内容了。

comments powered by Disqus