一个被两个闭包修改的变量

3
考虑以下(人为)的方式将 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中该怎么做呢?

3个回答

7
接受的答案是最习惯用的方法,但在某些情况下如果附加参数不起作用,例如需要将闭包传递给第三方代码并且该代码将无需参数调用它,则可以使用另一种替代方法。此时,您可以使用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);
}

6

按照设计,闭包在创建时必须将其所使用的外部对象封装起来。在这种情况下,闭包借用了外部的x对象。因此,编译器会在创建闭包f时向您解释它可变地借用了x,并且当您创建闭包g时,无法再次借用它。

为了编译它,您不能封装任何想要更改的外部对象。相反,您可以直接将对象作为闭包的参数传递(可能更易读)。这样,您描述了闭包接受某些类型的对象,但您还没有使用/传递任何实际对象。只有在调用闭包时才会借用该对象。

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
}

1

为了进一步解释Alex Larionov的答案:你应该把闭包看作一个可调用的结构体,捕获的任何内容都会被设置为结构体字段,然后在函数体内隐式地对其进行引用。这些字段的使用方式也决定了闭包是Fn、FnMut还是FnOnce,基本上取决于如果将方法写成长格式,它是否需要&self&mut selfself

这里

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 如果我们在夜间使用相关的不稳定功能,我们可以接近一点。这基本上就是rustc在幕后为我们所做的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接