一个被两个闭包修饰的变量
A variable modified by two closures
考虑以下(设计的)方法将 x
递增 9
。
fn main() {
let mut x = 0;
let mut f = || {
x += 4;
};
let _g = || {
f();
x += 5;
};
}
error[E0499]: cannot borrow `x` as mutable more than once at a time
--> x.rs:6:12
|
3 | let mut f = || {
| -- first mutable borrow occurs here
4 | x += 4;
| - first borrow occurs due to use of `x` in closure
5 | };
6 | let _g = || {
| ^^ second mutable borrow occurs here
7 | f();
| - first borrow later captured here by closure
8 | x += 5;
| - second borrow occurs due to use of `x` in closure
error: aborting due to previous error
For more information about this error, try `rustc --explain E0499`.
所以,它不起作用。如何制作像上面这样的算法来修改闭包中的变量并从中调用另一个也修改变量的闭包?
在传统语言中这很容易。在 Rust 中做什么?
根据设计,闭包必须包含 创建时在其中使用的外部对象,在我们的例子中,闭包必须借用外部x
对象。因此,正如编译器向您解释的那样,在创建闭包 f
时,它会可变地借用 x
,并且当您创建闭包 g
.
时,您不能再次借用它
为了编译它,您不能包含任何您想要更改的外部对象。相反,您可以直接将对象作为闭包的参数传递(可以说它更具可读性)。这样,您 描述 闭包接受某种类型的对象,但您还 use/pass 任何实际对象。这个对象只会在你调用闭包的时候被借用
fn main() {
let mut x = 0;
let f = |local_x: &mut i32| { // we don't enclose `x`, so no borrow yet
*local_x += 4;
};
let _g = |local_x: &mut i32| { // we don't enclose `x`, so no borrow yet
f(local_x);
*local_x += 5;
};
_g(&mut x); // finally we borrow `x`, and this borrow will later move to `f`,
// so no simultaneous borrowing.
println!("{}", x); // 9
}
公认的答案是最惯用的方法,但还有一种替代方法在附加参数不起作用的情况下很有用,例如,当您需要将闭包传递给第三方代码时不带参数地调用它。在这种情况下,您可以使用 Cell
,一种内部可变性形式:
use std::cell::Cell;
fn main() {
let x = Cell::new(0);
let f = || {
x.set(x.get() + 4);
};
let g = || {
f();
x.set(x.get() + 5);
};
f();
g();
assert_eq!(x.get(), 13);
}
扩展 Alex Larionov 的回答:您应该将闭包视为可调用结构,您捕获的任何内容都被设置为结构的一个字段,然后在函数体内隐式解除引用。这些字段 used 的方式也决定了闭包是 Fn、FnMut 还是 FnOnce,基本上是方法采用 &self
、&mut self
还是 self
如果它是手写的。
这里
fn main() {
let mut x = 0;
let mut f = || {
x += 4;
};
// ...
}
基本上转化为:
struct F<'a> { x: &'a mut u32 }
impl F<'_> {
fn call(&mut self) {
*self.x += 4
}
}
fn main() {
let mut x = 0;
let mut f = F { x: &mut x };
// ...
}
从这里可以看出,f
一创建,x
就被可变地借用了,所有这一切都意味着。
通过这种部分脱糖,我们可以看到本质上相同的错误:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e129f19f25dc61de8a6f42cdca1f67b5
如果我们在 nightly 上使用相关的 unstable 特性,我们可以得到 just a bit closer。这几乎就是 rustc 在幕后为我们所做的。
考虑以下(设计的)方法将 x
递增 9
。
fn main() {
let mut x = 0;
let mut f = || {
x += 4;
};
let _g = || {
f();
x += 5;
};
}
error[E0499]: cannot borrow `x` as mutable more than once at a time
--> x.rs:6:12
|
3 | let mut f = || {
| -- first mutable borrow occurs here
4 | x += 4;
| - first borrow occurs due to use of `x` in closure
5 | };
6 | let _g = || {
| ^^ second mutable borrow occurs here
7 | f();
| - first borrow later captured here by closure
8 | x += 5;
| - second borrow occurs due to use of `x` in closure
error: aborting due to previous error
For more information about this error, try `rustc --explain E0499`.
所以,它不起作用。如何制作像上面这样的算法来修改闭包中的变量并从中调用另一个也修改变量的闭包?
在传统语言中这很容易。在 Rust 中做什么?
根据设计,闭包必须包含 创建时在其中使用的外部对象,在我们的例子中,闭包必须借用外部x
对象。因此,正如编译器向您解释的那样,在创建闭包 f
时,它会可变地借用 x
,并且当您创建闭包 g
.
为了编译它,您不能包含任何您想要更改的外部对象。相反,您可以直接将对象作为闭包的参数传递(可以说它更具可读性)。这样,您 描述 闭包接受某种类型的对象,但您还 use/pass 任何实际对象。这个对象只会在你调用闭包的时候被借用
fn main() {
let mut x = 0;
let f = |local_x: &mut i32| { // we don't enclose `x`, so no borrow yet
*local_x += 4;
};
let _g = |local_x: &mut i32| { // we don't enclose `x`, so no borrow yet
f(local_x);
*local_x += 5;
};
_g(&mut x); // finally we borrow `x`, and this borrow will later move to `f`,
// so no simultaneous borrowing.
println!("{}", x); // 9
}
公认的答案是最惯用的方法,但还有一种替代方法在附加参数不起作用的情况下很有用,例如,当您需要将闭包传递给第三方代码时不带参数地调用它。在这种情况下,您可以使用 Cell
,一种内部可变性形式:
use std::cell::Cell;
fn main() {
let x = Cell::new(0);
let f = || {
x.set(x.get() + 4);
};
let g = || {
f();
x.set(x.get() + 5);
};
f();
g();
assert_eq!(x.get(), 13);
}
扩展 Alex Larionov 的回答:您应该将闭包视为可调用结构,您捕获的任何内容都被设置为结构的一个字段,然后在函数体内隐式解除引用。这些字段 used 的方式也决定了闭包是 Fn、FnMut 还是 FnOnce,基本上是方法采用 &self
、&mut self
还是 self
如果它是手写的。
这里
fn main() {
let mut x = 0;
let mut f = || {
x += 4;
};
// ...
}
基本上转化为:
struct F<'a> { x: &'a mut u32 }
impl F<'_> {
fn call(&mut self) {
*self.x += 4
}
}
fn main() {
let mut x = 0;
let mut f = F { x: &mut x };
// ...
}
从这里可以看出,f
一创建,x
就被可变地借用了,所有这一切都意味着。
通过这种部分脱糖,我们可以看到本质上相同的错误:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e129f19f25dc61de8a6f42cdca1f67b5
如果我们在 nightly 上使用相关的 unstable 特性,我们可以得到 just a bit closer。这几乎就是 rustc 在幕后为我们所做的。