JMC*_*JMC 4 closures rust borrow-checker
当尝试将引用重新分配到闭包内部其他位置时,我注意到一个我无法解释的奇怪行为,如以下最小示例所示:
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
};
}
Run Code Online (Sandbox Code Playgroud)
仅当引用为:时才会产生以下错误&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;
| ^^^^^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
这个错误在这里到底意味着什么?既然很明显闭包只有在foo2还活着的时候才活着,为什么这样做是不安全的呢?为什么参考与否很重要&mut?
当从作用域线程中尝试相同的操作时,它永远不会编译,无论有没有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
});
});
}
Run Code Online (Sandbox Code Playgroud)
删除后mut,程序编译不会出错。为什么这里的行为与第一个示例不同,第一个示例的删除mut可以满足编译器的要求?
我的研究让我相信这可能与闭包的 FnOnce、FnMut 和 Fn Traits 有关,但我被困住了。
考虑以下代码:
fn main() {
let mut foo: i32 = 5;
let mut foo2: i32 = 6;
let mut borrower = &mut foo;
let mut called = false;
let mut c = || {
if !called {
borrower = &mut foo2;
called = true;
} else {
foo2 = 123;
}
};
c();
c();
*borrower = 456;
}
Run Code Online (Sandbox Code Playgroud)
如果编译器按照您想要的方式查看代码,则此代码将是有效的:我们不会foo2在当前分支中借用,因此它不会被借用。但这段代码显然不是:我们在借用之前的闭包foo2调用时进行了变异。
如果您问编译器如何解决这个问题,那么我们需要看看脱糖的闭包是什么样子的。
闭包脱糖为实现Fn特征系列的结构。我们的闭包脱糖的大致方式如下:
struct Closure<'borrower, 'foo2> {
borrower: &'borrower mut &'foo2 mut i32,
foo2: &'foo2 mut i32,
}
// Forward `FnOnce` to `FnMut`. This is not really relevant for us, and I left it only for completeness.
impl FnOnce<()> for Closure<'_, '_> {
type Output = ();
extern "rust-call" fn call_once(mut self, (): ()) -> Self::Output {
self.call_mut(())
}
}
impl<'borrower, 'foo2> FnMut<()> for Closure<'borrower, 'foo2> {
extern "rust-call" fn call_mut<'this>(&'this mut self, (): ()) -> Self::Output {
*self.borrower = self.foo2;
}
}
// let mut c = || {
// borrower = &mut foo2;
// };
let mut c = Closure {
borrower: &mut borrower,
foo2: &mut foo2,
}
Run Code Online (Sandbox Code Playgroud)
看到问题了吗?我们正在尝试分配self.foo2给*self.borrower,但我们无法移出,self.foo因为我们只有对 的可变引用self。self我们可以可变地借用它,但只能在-的生命周期内借用'this,但这还不够。我们需要完整的一生foo2。
但是,当引用不可变时,我们不需要移出self.foo2- 我们只需复制它即可。这将创建一个具有所需生命周期的引用,因为不可变引用是Copy.
它想要在我带注释的代码中,没有Mutex(没有move,我希望很明显为什么它不能与 一起工作)的原因move是,所以编译器知道我们不能两次调用闭包。从技术上讲,我们有,也没有,所以我们可以移出它的领域。spawn()FnOnceself&mut self
如果我们FnOnce也强制的话,它会起作用:
fn force_fnonce(f: impl FnOnce()) {}
force_fnonce(|| {
borrower = &mut foo2;
});
Run Code Online (Sandbox Code Playgroud)
它不适用于您的作用域线程片段,即使它需要FnOnce,也是完全不同的:这又是因为move. 因此,foo2对于闭包而言,它是本地的,借用它会产生一个仅在闭包中有效的引用,因为当闭包退出时它会被销毁。修复它需要借用foo2而不是移动它。我们无法摆脱move因为aa,所以我们需要部分移动闭包捕获。方法如下:
fn main() {
let mut foo: i32 = 5;
let mut foo2: i32 = 6;
let a = Arc::new(Mutex::new(&mut foo));
println!("{}", a.lock().unwrap());
thread::scope(|s| {
let aa = a.clone();
let foo2_ref = &mut foo2;
s.spawn(move || {
*aa.lock().unwrap() = foo2_ref;
});
});
}
Run Code Online (Sandbox Code Playgroud)
这段代码确实可以编译,即使使用&mut.
| 归档时间: |
|
| 查看次数: |
135 次 |
| 最近记录: |