来谈谈闭包
LI Rui

Rust里面的闭包

Rust by Example中是这么介绍闭包的:The syntax and capabilities of closures make them very convenient for on the fly usage. Calling a closure is exactly like calling a function. However, both input and return types can be inferred and input variable names must be specified.

示例代码如下:

1
2
3
4
5
6
fn main() {
fn foo(i: i32) -> i32 { i + 1 }
let bar = |i| i + 1;

assert_eq!(foo(1), bar(1));
}

从这么短的一行代码结合上面的介绍我们可以知道:

  • Rust中的闭包捕获变量和返回值是可以被推断或者明确指定的,在被推断后,我们不能再传入其他类型,如bar(1.1)
  • 我们需要指定捕获变量的名称,当然_也是可以的。

其实说了像没说。我们再来看看The Rust Programming Language是怎么介绍闭包的:Rust’s closures are anonymous functions you can save in a variable or pass as arguments to other functions. You can create the closure in one place and then call the closure to evaluate it in a different context. Unlike functions, closures can capture values from the scope in which they’re defined. We’ll demonstrate how these closure features allow for code reuse and behavior customization.

其中最重要的是:闭包是匿名函数,可以用来存入变量或者给其他函数传递参数。

我们再来写点代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use std::thread;
use std::time::Duration;

fn enough_sleep(hours: i32) {
let heavy_work = || {
println!("Calculating...");
thread::sleep(Duration::from_secs(2));
8 - hours
};

if hours < 8 {
if hours < 6 {
println!("Too little sleep time! Get {} more hours to sleep!", heavy_work());
} else {
println!("You need to sleep another {} hours.", heavy_work());
}
} else {
println!("You are good to go!");
}
}

fn main() {
enough_sleep(10);
enough_sleep(6);
enough_sleep(5);
}

代码的输出结果是:

1
2
3
4
5
You are good to go!
Calculating...
You need to sleep another 2 hours.
Calculating...
Too little sleep time! Get 3 more hours to sleep!

🤔稍微像样点了,这里我们使用了闭包去计算还需要多久时间才能达到8小时睡眠(很遗憾,作者都睡不到8小时),在计算过程中休眠了2秒钟来模拟我们写的计算过程很高级。或许你已经看出来我们使用闭包享受了下面的好处:

  • 我们捕获了上下文中的hours变量!这是函数做不到的。
  • 我们在需要结果的时候才会去进行计算结果,还是说了像没说。

那么让我们把第二条说了像没说的话说清楚,我们来写点更加fancy的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
use std::thread;
use std::time::Duration;
use std::collections::HashMap;

struct Cacher<T>
where
T: Fn(u32) -> u32,
{
calculation: T,
value: HashMap<u32, u32>,
}

impl<T> Cacher<T>
where
T: Fn(u32) -> u32,
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: HashMap::new(),
}
}

fn value(&mut self, arg: u32) -> u32 {
if let Some(ret) = self.value.get(&arg) {
*ret
} else {
let ret = (self.calculation)(arg);
self.value.insert(arg, ret);
ret
}
}
}

fn main() {
let mut cacher = Cacher::new(|x| {
println!("Calculating...");
thread::sleep(Duration::from_secs(2));
x * 2
});
println!("{}", cacher.value(2));
println!("{}", cacher.value(2));
println!("{}", cacher.value(3));
}

对应的输出如下:

1
2
3
4
5
Calculating...
4
4
Calculating...
6

😍好像更不错了,我们实现了一个Cacher结构体,这个结构体包含了一个闭包表示我们需要完成怎样的计算,然后使用了一个HashMap保存计算的值作为缓存。如果需到同样的值,我们可以检查是否已经计算过,这样可以节省时间。同时我们可以传入不同 的闭包完成不同的计算。

在Rust中,我们也经常使用闭包来配合迭代器去生成一个新的迭代器,以对特殊元素进行某种操作。

参考资料

  • Rust by Example
  • The Rust Programming Language(译名:Rust权威指南)
  • 本文标题:来谈谈闭包
  • 本文作者:LI Rui
  • 创建时间:2021-12-27 09:40:00
  • 本文链接:https://www.lirui.tech/post/2021/0881704f3183.html
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-SA 许可协议。转载请注明出处!