如何在Rust中同时使用Vec中的多个项?

8

我一直在和借用检查器作斗争……我想做的主要事情是这样的:

#[derive(Debug)]
struct SomeStruct {
    value: String,
}

impl SomeStruct {
    fn new(value: &str) -> SomeStruct {
        SomeStruct { value: value.to_string() }
    }

    fn change_value(&mut self, new_value: &str) {
        self.value = new_value.to_string();
    }
}

fn main() {
    let mut my_vec = vec![
        SomeStruct::new("foo"),
        SomeStruct::new("bar"),
    ];

    my_vec[0].change_value(my_vec[1].value.as_str());
}

这是一个非常泛化的我遇到问题的版本,以下是 stderr 错误信息:
error[E0502]: cannot borrow `my_vec` as immutable because it is also borrowed as mutable
  --> src/main.rs:22:30
   |
22 |     my_vec[0].change_value(my_vec[1].value.as_str());
   |     ------                 ^^^^^^                  - mutable borrow ends here
   |     |                      |
   |     |                      immutable borrow occurs here
   |     mutable borrow occurs here

因此,借用检查器不允许我两次借用向量(一次作为可变的,然后再作为不可变的),这一点我理解。但令人沮丧的是,我想修改向量中的一个元素,只读取另一个元素。我对Rust还不太熟悉(惊讶!),我不确定自己是否已经完全掌握了所有细节和设计选择。但这是应该能够工作的东西,但实际上却不能。我缺少什么,我应该怎么做才能使它工作(或类似的行为)?任何帮助都将不胜感激!
2个回答

11
我同意,这有点令人困惑。那么首先让我们看看,为什么Rust编译器不允许这样做。
为什么不允许
索引运算符[]是可以被重载的,这意味着语言的用户可以指定它的工作方式。Rust试图最小化编译器具有某些特殊知识的类型数量。因此,尽管它很受欢迎,Vec只是由库定义的普通类型。你可以写自己的Vec而不告诉编译器!
Vec还重载了索引运算符,以允许对向量进行索引。但是由于重载可以做任何事情,它始终可以返回向量的第一个元素!如果你假设索引运算符会做这样奇怪的事情,那么这段代码就不应该被允许:
my_vec[0].change_value(my_vec[1].value.as_str());

因为my_vec[0]my_vec[1]引用了同一个值。

如何使其工作

当然,索引操作符并没有被实现成这样愚蠢的方式,我们都知道。为了得到对向量中不同元素的两个引用(其中至少一个是可变的),我们必须使用一些特殊函数而不是索引操作符。有很多方法可以做到:

我无法告诉你要使用哪种方法,因为我不知道您的确切用例。但是为了修复您的示例,您可以编写:

let (head, tail) = my_vec.split_first_mut();
head.change_value(tail[0].value.as_str());

谢谢!非常好的解释。为了确保我理解:.split 方法创建了对我的 Vec 的视图,是吗?因此,实际对象仍然留在向量中,但在执行您的代码片段后,它们被更改成我想要的方式? - tronje
1
@tronje 是的,完全正确。例如,split_first_mut() 返回一个可变引用,指向向量中的第一个元素(该元素仍留在向量中),以及一个可变切片(也可以称为“视图”)。我们只是引用原始数据。 - Lukas Kalbertodt

2

你说得对,不能同时以不可变和可变的方式借用一个对象;为了使其工作,您可以执行以下操作:

let new_value = my_vec[1].value.clone();
my_vec[0].change_value(&new_value);

当你对myvec[1]执行clone()操作时,你不再借用myvec,可以在下一行中自由使用new_value


谢谢你的回答!在 Rust 中这是常见的做法吗?感觉有些别扭,特别是如果我的 .value 不是一个字符串,而是一个更大更复杂的对象,但我猜也没有其他什么选择了吧… - tronje
@tronje 实际上,应该避免克隆。当然,在某些代码中性能根本不重要,但通常为了满足借用检查器而进行克隆应该被避免。尽管有时它是最简单的解决方案。 - Lukas Kalbertodt
@tronje 这只是针对简单情况的简单解决方案;对于大型对象应该避免使用。 - ljedrz

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