可变借用和不可变借用在闭包中的区别?

7
我无法弄清楚如何使以下代码正常工作。我认为需要在闭包中借用 &mut Vec,但我不知道该如何表达。这段代码是从一个更大的函数中提取出来的,但显示了相同的错误。
fn main() {
    let mut v = vec![0; 10];

    let next = |i| (i + 1) % v.len();

    v[next(1usize)] = 1;

    v.push(13);

    v[next(2usize)] = 1;    
}

错误:
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> a.rs:9:5
  |
5 |     let next = |i| {
  |                --- immutable borrow occurs here
6 |         (i + 1) % v.len()
  |                   - first borrow occurs due to use of `v` in closure
...
9 |     v[next(1usize)] = 1;
  |     ^ ---- immutable borrow later used here
  |     |
  |     mutable borrow occurs here

error: aborting due to previous error

1
请注意,这似乎与 v[(1usize + 1) % v.len()] = 1; 没有区别。您同时拥有可变和不可变的借用。 - Schwern
1
@Schwern 是的。你是对的,同样的错误。那应该怎么修复呢? - Tumbleweed53
“let vlen = (1usize + 1) % v.len(); v[vlen] = 1;” 是简单的解决方法,因此在您进行变异时没有东西可以保留不可变借用。对于您想要完成的任务,我觉得这就是迭代器的作用。 - Schwern
1
现在假设逻辑更加复杂,需要封装在闭包中以便重复使用。那该如何实现呢? - Tumbleweed53
我认为它不起作用。Rust 对事物的工作方式有非常严格的想法,我认为闭包并没有其他语言中可能习惯的那种自由度。闭包必须保持借用,这意味着您不能对同一向量进行可变借用以进行其他任何操作。您可能可以使用生命周期来做一些事情(我无法帮助),但我认为闭包不会给您那种控制权。迭代器可以。 - Schwern
我认为我需要一个闭包来借用 &mut Vec。但这并不会有所帮助,因为你将拥有两个同时存在的可变借用,这是不被允许的。 - Alexey Romanov
4个回答

5

如果您真的想使用闭包实现它,您需要通过参数传递向量:

let next = |v: &Vec<_>, i| (i + 1) % v.len();

这将使闭包每次调用时进行借用,而不是在作用域中捕获。但您仍然需要分开借用:
let j = next(&v, 1usize);
v[j] = 1;

为了让你的生活更方便,你可以将所有东西都放在闭包中:
let next = |v: &mut Vec<_>, i, x| {
    let j = (i + 1) % v.len();
    v[j] = x;
};

这使得你可以轻松地执行以下操作:

next(&mut v, 1usize, 1);
next(&mut v, 2usize, 2);
// etc.

这个模式适用于你写一个闭包只是为了避免本地代码重复的情况(我猜这就是你问的原因)。

1
谢谢您的回复。是的,我主要是想避免本地代码重复 - 您的示例很有帮助。我现在明白了。闭包在其生命周期内不可变地捕获了v,这意味着我以后无法对其进行更改。 - Tumbleweed53
不客气!是的,闭包捕获了 v,只要它还在周围就会成为一个问题。 - Acorn

2

由于闭包只需要 Vec 的长度,因此您可以在闭包之前获取它。这样就避免了整个借用问题,因为闭包不再需要借用 v

fn main() {
    let mut v = vec![0; 10];

    let len = v.len();
    let next = |i| (i + 1) % len;

    v[next(1usize)] = 1;
}

假设你的闭包不依赖于其他内容,那么你可以定义一个带有执行该操作的方法的特质,而不是使用闭包。
为了简化起见,我们将这个特质称为VecExt,方法称为set。
trait VecExt<T> {
    fn set(&mut self, index: usize, value: T);
}

impl<T> VecExt<T> for Vec<T> {
    fn set(&mut self, index: usize, value: T) {
        let len = self.len();
        self[(index + 1) % len] = value;
    }
}

fn main() {
    let mut v = vec![0; 10];

    v.set(1, 1);

    v.push(13);

    v.set(2, 1);
}

在更真实的例子中,len 将是动态的。而且谁知道,也许我想在闭包内对 Vec 做其他事情。这就是在此网站上创建最小化的可重现性所面临的挑战。 - Tumbleweed53
2
@Tumbleweed53,您能否更新示例以更好地反映问题/用例? - vallentin
更新以展示向量的长度可能会改变。 - Tumbleweed53
@Tumbleweed53 是你实际的代码,特别是闭包是否依赖于其他东西?如果不是,那么你可以定义一个带有执行当前闭包功能的方法的 trait。这样行吗? - vallentin

1
一个闭包可能不是正确的工具。编译器不满意的原因是你的闭包已经引用了你的Vec,但是当闭包引用仍然存在时,你正在尝试改变Vec。根据Rust的借用规则,这是不允许的。
最直接的方法是将数据存储在结构体中,并使next成为一个成员函数。这样,就没有闭包引用;它只能在需要时检查长度。
struct WrapVec<T>(Vec<T>);

impl<T> WrapVec<T> {
    fn wrap_next(&mut self, index: usize) -> &mut T {
        let index = (index + 1) % self.0.len();
        &mut self.0[index]
    }
}

fn main() {
    let mut v = WrapVec(vec![0; 10]);
    *v.wrap_next(1) = 1;
    v.0.push(13);
    *v.wrap_next(2) = 1;
}

如果你想将此函数应用于任何Vec,那么定义一个新的特质可能会很有用。例如:
trait WrapNext<T> {
    fn wrap_next(&mut self, index: usize) -> &mut T;
}

impl<T> WrapNext<T> for Vec<T> {
    fn wrap_next(&mut self, index: usize) -> &mut T {
        let index = (index + 1) % self.len();
        &mut self[index]
    }
}

fn main() {
    let mut v = vec![0; 10];
    *v.wrap_next(1) = 1;
    v.push(13);
    *v.wrap_next(2) = 1;
}

1
除了其他答案之外,您可以使用宏来限定 v 的范围,这样您就不必在每次调用时传递它。
fn main() {
    let mut v = vec![0; 10];

    macro_rules! next {
        // rule to get an index
        ($i: expr) => {
            ($i + 1) % v.len()
        };
        // rule to mutate the vector
        ($i: expr => $v: expr) => {{
            let ind = next!($i);
            v[ind] = $v;
        }};
    };
    // get an index
    let ind = next!(1usize);
    // mutate the vector
    v[ind] = 1;
    v.push(13);
    // or, with the mutation syntax
    next!(2usize => 3);
    println!("{:?}", v);
}

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