tao-of-rust-codes
tao-of-rust-codes copied to clipboard
6.2.3 闭包和所有权。能不能总结一个正交的规则
环境变量语义 | 环境变量操作 | 有无move | 结果是哪种闭包 | 环境变量被怎么处理 |
---|---|---|---|---|
复制语义 | 读环境变量 | 有 move | Fn 闭包 | &env |
复制语义 | 读环境变量 | 无 move | Fn闭包 | &env |
复制语义 | 修改环境变量 | 有 move | ||
复制语义 | 修改环境变量 | 无 move | ||
移动语义 | 读环境变量 | 有 move | ||
移动语义 | 读环境变量 | 无 move | ||
移动语义 | 修改环境变量 | 有 move | FnMut 闭包 | 转移所有权 |
移动语义 | 修改环境变量 | 无 move | FnMut 闭包 | &mut env |
现在书的内容看完之后有点懵,反而把我以前的理解搞乱了。
比如这段代码
fn main() {
let mut s = 1;
let c = || {
println!("{:?}", s);
println!("{:p}", &s);
};
c();
c();
let d = &mut s;
println!("{:p}", d);
println!("{:?}", s);
println!("{:p}", &s);
}
按 179 页第二段说,闭包会以「不可变借用捕获了环境中的自由变量」,那不应该还能在闭包外面使用 &mut 的。
希望作者能帮我解惑。
@frostRed 感谢反馈,后续在勘误表里尝试总结一下。不过也希望你可以自己总结一下,分享出来。
在 frostRed 的表格上进行了补全,大家看看没有错误
环境变量语义 | 环境变量操作 | 有无 move | 闭包 Trait | 环境变量被怎么处理 | 闭包能否Copy |
---|---|---|---|---|---|
复制语义 | 读环境变量 | 有 move | Fn | copy | √ |
复制语义 | 读环境变量 | 无 move | Fn | immutable borrow | √ |
复制语义 | 修改环境变量 | 有 move | FnMut | copy | √ |
复制语义 | 修改环境变量 | 无 move | FnMut | mutable borrow | × |
移动语义 | 读环境变量 | 有 move | Fn | move | × |
移动语义 | 读环境变量 | 无 move | Fn | immutable borrow | √ |
移动语义 | 修改环境变量 | 有 move | FnMut | move | × |
移动语义 | 修改环境变量 | 无 move | FnMut | mutable borrow | × |
移动语义 | 消耗环境变量 | 有 move | FnOnce | move | × |
移动语义 | 消耗环境变量 | 无 move | FnOnce | move | × |
闭包 Trait
- 没有捕获环境变量以及只是不可变借用环境变量(捕获不可变引用的值)的闭包自动实现 Fn Trait
- 可变借用环境变量的闭包自动实现 FnMut Trait
- 消耗环境变量的闭包自动实现 FnOnce Trait
- 因为 FnOnce 是 FnMut 的父 Trait,FnMut又是Fn的父Trait,所以实现某个Trait时也会自动实现其父 Trait
move 作用
强行让闭包获取捕获的环境变量的所有权,这可能改变闭包本身的性质。下面例子就是,如果没有 move,闭包捕获的是一个 Copy 语意的不可变引用,所以闭包本身也会自动实现 Copy。在闭包前加上 move 强行转移所有权,导致闭包不能自动实现 Copy。
fn main() {
let s = "hello".to_owned();
let f = move || {
println!("{}", s);
};
f();
// foo(f); // 因为 string move 到闭包环境里,让闭包不能自动实现 Copy
}
fn foo<F: Fn() + Copy>(f: F) {
f()
}
同样的道理,用 move 可以让本来不能 Copy 的闭包自动实现 Copy (可变引用没有实现 Copy)
fn main() {
let mut x = 0;
let mut f = move || {
x += 1;
println!("{}", x);
};
f();
foo(f);
f();
foo(f);
println!("{}", x);
}
fn foo<F: FnMut() + Copy>(mut f: F) {
f()
}
运行结果,作为函数参数传递的闭包每次复制新的闭包环境:
Standard Error
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 0.85s
Running `target/debug/playground`
Standard Output
1
2
2
3
0
闭包能否自动实现 Copy
取决于捕获的变量类型,如果捕获的是不可变引用、 move 的值本身实现了 Copy,闭包就能自动实现 Copy。
@yim7 辛苦了,已加精。
复制语义 | 读环境变量 | 无 move | Fn | copy | √
这里环境变量的处理方式不是copy, 而是immutable borrow.
fn main() {
let mut a = 1;
let print = || {&a;};
let aa = &mut a;
print();
}
error[E0502]: cannot borrow `a` as mutable because it is also borrowed as immutable
--> main.rs:4:14
|
3 | let print = || {&a;};
| -- - first borrow occurs due to use of `a` in closure
| |
| immutable borrow occurs here
4 | let aa = &mut a;
| ^^^^^^ mutable borrow occurs here
5 | print();
| ----- immutable borrow later used here
error: aborting due to previous error
闭包的类型与闭包中使用环境变量的方式, 以及环境变量是否Copy有关; 闭包中环境变量的捕获方式, 与闭包中环境变量的使用方式, 以及是否有move有关; 至于是复制还是移动, 则与环境变量类型本身的语义有关.
可以确认一下是不是这样.
感谢纠正疏漏之处@loxp