当试图从闭包内部重新分配一个引用到其他地方时,我注意到一个奇怪的行为,我无法解释,通过这个最小的例子来展示:
fn main() {
let mut foo: i32 = 5;
let mut foo2: i32 = 6;
let mut borrower = &mut foo; // compiles OK without mut here and below
let mut c = || {
borrower = &mut foo2; // compiles OK without mut here and above
};
}
字符串
只有当引用为&mut
时,才会产生以下错误:
error[E0521]: borrowed data escapes outside of closure
--> src/main.rs:25:9
|
23 | let mut borrower = &mut foo;
| ------------ `borrower` declared here, outside of the closure body
24 | let mut c = || {
25 | borrower = &mut foo2;
| ^^^^^^^^^^^^^^^^^^^^
型
这个错误到底是什么意思呢?既然很明显闭包只有在foo2
仍然存在时才存在,那么为什么这样做是不安全的呢?为什么&mut
引用很重要?
当从a作用域线程尝试相同的操作时,无论是否使用mut
,它都不会编译:
fn main() {
let mut foo: i32 = 5;
let mut foo2: i32 = 6;
let a = Arc::new(Mutex::new(&mut foo)); // removing mut does NOT fix it
println!("{}", a.lock().unwrap());
thread::scope(|s| {
let aa = a.clone();
s.spawn(move ||{
*aa.lock().unwrap() = &mut foo2; // removing mut does NOT fix it
});
});
}
型
删除mut
后,程序编译时没有错误。为什么这里的行为与第一个例子不同,在第一个例子中,删除mut
满足了编译器的要求?
我的研究使我相信它可能与闭包的FnOnce、FnMut和Fn特性有关,但我被卡住了。
1条答案
按热度按时间velaa5lx1#
请考虑下列程式码:
字符串
如果编译器按照您的要求查看代码,则此代码将是有效的:我们在当前分支中没有借用
foo2
,所以它不是借用的。但这段代码显然不是:当foo2
被借用时,我们改变它,* 从前面的闭包调用 *。如果你想知道编译器是如何计算出来的,那么我们需要看看去糖闭包是什么样子的。
关闭实现
Fn
trait家族的结构体的去糖。以下是我们的闭包如何大致去糖:型
看到问题了吗我们试图将
self.foo2
分配给*self.borrower
,但是我们不能移出self.foo
,因为我们只有一个对self
的可变引用。我们可以可变地借用它,但只能在self
-'this
的生命周期内使用,这是不够的。我们需要完整的生命周期foo2
。然而,当引用是不可变的时,我们不需要移出
self.foo2
--我们可以只 * 复制 * 它。这将创建一个具有所需生存期的引用,因为不可变引用是Copy
。我在代码中引入了一个注解,没有
Mutex
(没有move
,我希望很明显为什么它不能与move
一起工作),原因是spawn()
需要FnOnce
,所以编译器知道我们不能调用闭包两次。从技术上讲,我们有self
而不是&mut self
,因此我们可以移出它的字段。如果我们强制
FnOnce
也可以工作:型
它不能与你的scoped threads代码段一起工作的原因是完全不同的,即使它需要
FnOnce
:又是因为move
。因此,foo2
是闭包的本地引用,借用它会产生一个只在闭包中有效的引用,因为它在闭包退出时被销毁。修复它需要借用foo2
而不是移动它。由于aa
,我们无法摆脱move
,因此我们需要 * 部分移动 * 闭包捕获。方法如下:型
即使使用
&mut
,这段代码也确实可以编译。