Appearance
Rust 当中的闭包
闭包是一个 Java 当中没有的内容。它是一种匿名函数,可以捕获自己所在定义域当中的变量而不传入这个变量。 在java当中方法当中的方法也可以捕捉外部的变量,但是是有代价的,就是这个捕捉的变量需要是一个有效final。
修改值的闭包情况
rust
fn main() {
work(26);
}
fn work(plan: u32) {
let mut num_of_water_cups = 1;
let mut drink_water = move |num: u32| {
num_of_water_cups += num;
num_of_water_cups
};
if plan < 25 {
println!("plan<25");
} else {
println!("plan>=25");
// 第一次调用闭包
let r1 = drink_water(3);
println!("After first drinking, num_of_water_cups is: {}", r1);
// 第二次调用闭包
let r2 = drink_water(2);
println!("After second drinking, num_of_water_cups is: {}", r2);
// println!(num_of_water_cups);
}
}
运行后结果是:
plan>=25
After first drinking, num_of_water_cups is: 4
After second drinking, num_of_water_cups is: 6
添加了 move
后,会把 num_of_water_cups
这个变量的所有权放入到这个小闭包当中了,也就是说在外部我们已经不能访问它了。我们可以通过在闭包当中返回这个变量,来实时获取这个变量当前结果。
第一次,我们加入了 3,然后在闭包当中的这个变量 1 + 3 变成了 4。 第二次我们再加入 2,从刚才的 4 再加 2,变成了 6。
这个变量被保存在了闭包的定义域当中,从大作用域进入了小作用域,我们可以通过在闭包当中返回它的值的方式来获取它的实时情况。
不修改值的闭包情况
rust
fn main() {
work(26);
}
fn work(plan: u32) {
let num_of_water_cups = 1;
let drink_water = |num: u32| {
println!("num_of_water_cups ={} in closure and num is {}", num_of_water_cups, num);
num
};
if plan < 25 {
println!("plan<25");
} else {
println!("plan>=25");
drink_water(3);
drink_water(4);
println!("{}", num_of_water_cups);
}
}
在这个情境下得到的输出结果是:
plan>=25
num_of_water_cups =1 in closure and num is 3
num_of_water_cups =1 in closure and num is 4
1
可以看到在闭包当中我们是没有传入 num_of_water_cups
这个变量的,但是通过闭包可以捕获外部也就是闭包所在定义域变量的特性,在闭包内部成功打印了外部的 num_of_water_cups
这个变量。
闭包的三种 Trait:Fn、FnMut 和 FnOnce
这两种不同的闭包行为,在 Rust 的类型系统里分别对应三个特殊的 trait:
Fn trait
对应上面的第二个例子。闭包只对捕获的变量进行不可变借用(&
)。它可以在任何地方被多次调用。上面的第二个闭包实现了 Fn
trait。
rust
fn main() {
let x = 5;
// 这个闭包实现了 Fn trait
let closure = |y| x + y;
// 可以多次调用
println!("{}", closure(1)); // 6
println!("{}", closure(2)); // 7
// x 仍然可以在外部访问
println!("x is still: {}", x); // 5
}
FnMut trait
对应第一个例子中没有 move
的情况。闭包对捕获的变量进行可变借用(&mut
)。它能修改外部变量,但由于借用规则,必须将闭包本身声明为 mut
,并且不能有其他的借用同时存在。
rust
fn main() {
let mut x = 5;
// 这个闭包实现了 FnMut trait
let mut closure = |y| {
x += y;
x
};
println!("{}", closure(1)); // 6
println!("{}", closure(2)); // 8
// x 在闭包调用完成后仍然可以访问
println!("x is now: {}", x); // 8
}
FnOnce trait
对应第一个例子中有 move
的情况。闭包会获取捕获变量的所有权,因此只能被调用一次。上面的第一个 move
闭包实现了 FnOnce
trait。
虽然上面的例子中多次调用了它,那是因为 u32
实现了 Copy
trait,所以每次 move
过去的是一个拷贝,而不是所有权转移。如果捕获的是一个 String
,那么闭包就真的只能被调用一次了:
rust
fn main() {
let s = String::from("hello");
// 这个闭包实现了 FnOnce trait(因为 String 没有实现 Copy)
let closure = move |suffix: &str| {
format!("{} {}", s, suffix) // s 的所有权被移动到闭包中
};
println!("{}", closure("world")); // "hello world"
// 下面这行会编译错误,因为闭包只能调用一次
// println!("{}", closure("rust"));
// s 在外部也不能再访问了
// println!("{}", s); // 编译错误
}