何时需要将闭包变量设置为可变的?

4

为什么我需要将这个闭包变量变成可变的?因为闭包没有返回任何东西,因此在闭包变量中没有存储任何内容。这个闭包只是从环境中捕获一个值并对其进行递增操作。

fn main() {
    let mut x = 1;
    let mut y = || x = x + 1;
    y();
    println!("{}", x);
}

2
看起来你的问题可能已经在何时闭包实现Fn、FnMut和FnOnce?的答案中得到了回答。如果不是,请**[编辑]**你的问题以解释其中的区别。否则,我们可以将此问题标记为已经回答。 - Shepmaster
2个回答

11
基本上,这是由Rust的安全规则之一造成的后果——你不能通过可变引用的共享引用来修改任何东西。为了实现这一点,您链中所有引用都需要是可变的。
让我们将您示例中的闭包转换为大致等效的使用结构体的代码进行翻译:
struct Closure<'a> {
    x: &'a mut i32,
}

impl Closure<'_> {
    fn call(&mut self) {
        *self.x += 1;
    }
}

闭包使用可变引用捕获变量 x,以便能够修改它。当闭包被调用时,它通过可变引用接收 self,以便可以通过 self 指针改变 x

现在,如果你想调用这个闭包,你需要使它也是可变的:

let mut x = 1;
let mut y = Closure { x: &mut x };
y.call();

如果您不将y变为可变的, 您就不能将一个可变引用传递给call()方法。

上述类型安全规则的原因很容易理解: 如果您可以通过对可变引用的共享引用进行变更,那么很容易编写出多个玩家持有对同一内存位置的可变引用的代码,这违反了Rust引用的基本规则之一。


-1

你的情况完全相反。返回一个值不需要在闭包中存储任何东西。而捕获则需要在闭包中存储一些东西。在你的情况下,y 需要存储对 x 的引用。由于该引用是可变的,因此闭包需要是可变的。

在这里,你可以看到一个仅返回值(但未捕获任何内容)、一个捕获了一些内容、以及一个捕获了更多内容的闭包之间的大小差异。

fn main() {
    let mut a = 1;
    let mut b = 1;
    let mut c = 1;

    let small = || {
        return 10;
    };

    let mut bigger = || {
        a = a + 1;
    };

    let mut biggest = || {
        b = b + 1;
        c = c + 1;
    };

    small();
    bigger();
    biggest();

    println!("{}, {}, {}",
        std::mem::size_of_val(&small),
        std::mem::size_of_val(&bigger),
        std::mem::size_of_val(&biggest)
    );
}

输出:

0, 8, 16

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f48399e830f6a6e597338114ea30a409

为了完整起见,这里有一个捕获值但不改变它的闭包。你可以看到它不需要可变性。

fn main() {
    let x = 1;
    let y = || x;
    println!("{}, {}", y(), std::mem::size_of_val(&y));
}

2
你的回答不太清楚,我不明白为什么要谈论存储东西,这个闭包自动实现了FnMut,因为它捕获了一个可变引用,并且调用它时需要将其作为可变借用。但实际上,没有任何阻止这个函数像这样实现FnOnce https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1e96ed8833737d1feb696fe4b35dab4c。 - Stargateur
1
@Stargateur: 我提到存储东西,因为那似乎是原帖的困惑所在。 - Benjamin Lindley
1
由于该引用是可变的,闭包需要是可变的——这并不完全准确(或者至少不够精确)。这里有一个例子,其中闭包捕获了一个可变引用但本身不需要是可变的。 - Shepmaster

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