Rust从入门到实战系列三百四十四:闭包会捕获其环境

在健身计划生成器的例子中,我们只将闭包作为内联匿名函数来使用。不过闭包还有另一个函数所没有
的功能:他们可以捕获其环境并访问其被定义的作用域的变量。
有一个储存在 equal_to_x 变量中闭包的例子,它使用了闭包环境中的变量 x:
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
}
一个引用了其周围作用域中变量的闭包示例这里,即便 x 并不是 equal_to_x 的一个参数,equal_to_x 闭包也被允许使用变量 x,因为它与 equal_to_x
定义于相同的作用域。
函数则不能做到同样的事,如果尝试如下例子,它并不能编译:
fn main() {
let x = 4;
fn equal_to_x(z: i32) -> bool {
z == x
}
let y = 4;
assert!(equal_to_x(y));
}
这会得到一个错误:
$ cargo run
Compiling equal-to-x v0.1.0 (file:///projects/equal-to-x)
error[E0434]: can’t capture dynamic environment in a fn item
–> src/main.rs:5:14
|
5 | z == x
| ^
|
= help: use the || { ... } closure form instead
For more information about this error, try rustc --explain E0434.
error: could not compile equal-to-x due to previous error
编译器甚至会提示我们这只能用于闭包!
当闭包从环境中捕获一个值,闭包会在闭包体中储存这个值以供使用。这会使用内存并产生额外的开销,
在更一般的场景中,当我们不需要闭包来捕获环境时,我们不希望产生这些开销。因为函数从未允许捕
获环境,定义和使用函数也就从不会有这些额外开销。
闭包可以通过三种方式捕获其环境,他们直接对应函数的三种获取参数的方式:获取所有权,可变借用
和不可变借用。这三种捕获值的方式被编码为如下三个 Fn trait:
• FnOnce 消费从周围作用域捕获的变量,闭包周围的作用域被称为其 环境,environment。为了消
费捕获到的变量,闭包必须获取其所有权并在定义闭包时将其移动进闭包。其名称的 Once 部分代
表了闭包不能多次获取相同变量的所有权的事实,所以它只能被调用一次。
• FnMut 获取可变的借用值所以可以改变其环境
• Fn 从其环境获取不可变的借用值
当创建一个闭包时,Rust 根据其如何使用环境中变量来推断我们希望如何引用环境。由于所有闭包都
可以被调用至少一次,所以所有闭包都实现了 FnOnce 。那些并没有移动被捕获变量的所有权到闭包
内的闭包也实现了 FnMut ,而不需要对被捕获的变量进行可变访问的闭包则也实现了 Fn 。在示例 13-
12 中,equal_to_x 闭包不可变的借用了 x(所以 equal_to_x 具有 Fn trait),因为闭包体只需要读取 x 的值。
如果你希望强制闭包获取其使用的环境值的所有权,可以在参数列表前使用 move 关键字。这个技巧在
将闭包传递给新线程以便将数据移动到新线程中时最为实用。
第十六章讨论并发时会展示更多 move 闭包的例子,不过现在这里修改了示例 13-12 中的代码(作为演
示),在闭包定义中增加 move 关键字并使用 vector 代替整型,因为整型可以被拷贝而不是移动;注意
这些代码还不能编译:
fn main() {
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x;
println!(“can’t use x here: {:?}”, x);
let y = vec![1, 2, 3];
assert!(equal_to_x(y));
}
这个例子并不能编译,会产生以下错误:
$ cargo run
Compiling equal-to-x v0.1.0 (file:///projects/equal-to-x)
error[E0382]: borrow of moved value: x
–> src/main.rs:6:40
|
2 | let x = vec![1, 2, 3];
| - move occurs because x has type Vec<i32>, which does not implement the Copy trait
3 |
4 | let equal_to_x = move |z| z == x;
| -------- - variable moved due to use in closure
| |
| value moved into closure here
5 |
6 | println!(“can’t use x here: {:?}”, x);
| ^ value borrowed here after move
For more information about this error, try rustc --explain E0382.
error: could not compile equal-to-x due to previous error
x 被移动进了闭包,因为闭包使用 move 关键字定义。接着闭包获取了 x 的所有权,同时 main 就不再
允许在 println! 语句中使用 x 了。去掉 println! 即可修复问题。
大部分需要指定一个 Fn 系列 trait bound 的时候,可以从 Fn 开始,而编译器会根据闭包体中的情况告
诉你是否需要 FnMut 或 FnOnce。
为了展示闭包作为函数参数时捕获其环境的作用,让我们继续下一个主题:迭代器。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值