捕获
闭包本质上很灵活,无需注解就能根据功能需求自动适应。这使得捕获可以灵活地适应不同场景,有时移动,有时借用。闭包可以通过以下方式捕获变量:
- 通过引用:
&T - 通过可变引用:
&mut T - 通过值:
T
闭包优先通过引用捕获变量,仅在必要时才使用更底部的的捕获方式。
fn main() {
use std::mem;
let color = String::from("green");
// 打印 `color` 的闭包,立即借用(`&`)`color` 并
// 将借用和闭包存储在 `print` 变量中。借用状态
// 将持续到 `print` 最后一次使用。
//
// `println!` 只需要不可变引用参数,所以
// 不会施加更多限制。
let print = || println!("`color`: {}", color);
// 使用借用调用闭包。
print();
// `color` 可以再次被不可变借用,因为闭包只持有
// `color` 的不可变引用。
let _reborrow = &color;
print();
// `print` 最后一次使用后,允许移动或重新借用
let _color_moved = color;
let mut count = 0;
// 增加 `count` 的闭包可以接受 `&mut count` 或 `count`,
// 但 `&mut count` 限制更少,所以选择它。立即
// 借用 `count`。
//
// `inc` 需要 `mut` 因为内部存储了 `&mut`。因此,
// 调用闭包会修改 `count`,这需要 `mut`。
let mut inc = || {
count += 1;
println!("`count`: {}", count);
};
// 使用可变借用调用闭包。
inc();
// 闭包仍然可变借用 `count`,因为它稍后会被调用。
// 尝试重新借用会导致错误。
// let _reborrow = &count;
// ^ TODO:尝试取消注释这行。
inc();
// 闭包不再需要借用 `&mut count`。因此,
// 可以在没有错误的情况下重新借用
let _count_reborrowed = &mut count;
// 不可复制类型。
let movable = Box::new(3);
// `mem::drop` 需要 `T`,所以这里必须通过值获取。可复制类型
// 会被复制到闭包中,原始值保持不变。
// 不可复制类型必须移动,所以 `movable` 立即移动到闭包中。
let consume = || {
println!("`movable`: {:?}", movable);
mem::drop(movable);
};
// `consume` 消耗了变量,所以只能调用一次。
consume();
// consume();
// ^ TODO:尝试取消注释这行。
}
在竖线前使用 move 强制闭包获取捕获变量的所有权:
fn main() {
// `Vec` 是非复制语义。
let haystack = vec![1, 2, 3];
let contains = move |needle| haystack.contains(needle);
println!("{}", contains(&1));
println!("{}", contains(&4));
// println!("vec 中有 {} 个元素", haystack.len());
// ^ 取消上面这行的注释会导致编译时错误
// 因为借用检查器不允许在变量被移动后重用。
// 从闭包签名中移除 `move` 将导致闭包
// 不可变借用 _haystack_ 变量,因此 _haystack_ 仍可用,
// 取消注释上面的行不会导致错误。
}