无法作为可变借用,因为它也被作为不可变借用。

94

我正在学习Rust,但我不太明白为什么这不起作用。

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

#[derive(Debug)]
pub struct Graph {
    nodes: Vec<Box<Node>>,
}

fn mk_node(value: String) -> Node {
    Node { value }
}

pub fn mk_graph() -> Graph {
    Graph { nodes: vec![] }
}

impl Graph {
    fn add_node(&mut self, value: String) {
        if let None = self.nodes.iter().position(|node| node.value == value) {
            let node = Box::new(mk_node(value));
            self.nodes.push(node);
        };
    }

    fn get_node_by_value(&self, value: &str) -> Option<&Node> {
        match self.nodes.iter().position(|node| node.value == *value) {
            None => None,
            Some(idx) => self.nodes.get(idx).map(|n| &**n),
        }
    }
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn some_test() {
        let mut graph = mk_graph();

        graph.add_node("source".to_string());
        graph.add_node("destination".to_string());

        let source = graph.get_node_by_value("source").unwrap();
        let dest = graph.get_node_by_value("destination").unwrap();

        graph.add_node("destination".to_string());
    }
}

这里出现了错误 (playground)

error[E0502]: cannot borrow `graph` as mutable because it is also borrowed as immutable
  --> src/main.rs:50:9
   |
47 |         let source = graph.get_node_by_value("source").unwrap();
   |                      ----- immutable borrow occurs here
...
50 |         graph.add_node("destination".to_string());
   |         ^^^^^ mutable borrow occurs here
51 |     }
   |     - immutable borrow ends here

这个例子来自《Rust程序设计语言》,与我的情况非常相似,但它可以正常工作:

pub struct Queue {
    older: Vec<char>,   // older elements, eldest last.
    younger: Vec<char>, // younger elements, youngest last.
}

impl Queue {
    /// Push a character onto the back of a queue.
    pub fn push(&mut self, c: char) {
        self.younger.push(c);
    }

    /// Pop a character off the front of a queue. Return `Some(c)` if there /// was a character to pop, or `None` if the queue was empty.
    pub fn pop(&mut self) -> Option<char> {
        if self.older.is_empty() {
            if self.younger.is_empty() {
                return None;
            }

            // Bring the elements in younger over to older, and put them in // the promised order.
            use std::mem::swap;
            swap(&mut self.older, &mut self.younger);
            self.older.reverse();
        }

        // Now older is guaranteed to have something. Vec's pop method // already returns an Option, so we're set.
        self.older.pop()
    }

    pub fn split(self) -> (Vec<char>, Vec<char>) {
        (self.older, self.younger)
    }
}

pub fn main() {
    let mut q = Queue {
        older: Vec::new(),
        younger: Vec::new(),
    };

    q.push('P');
    q.push('D');

    assert_eq!(q.pop(), Some('P'));
    q.push('X');

    let (older, younger) = q.split(); // q is now uninitialized.
    assert_eq!(older, vec!['D']);
    assert_eq!(younger, vec!['X']);
}
3个回答

166
你的问题可以简化为一个MRE
// This applies to the version of Rust this question
// was asked about; see below for updated examples.
fn main() {
    let mut items = vec![1];
    let item = items.last();
    items.push(2);
}

error[E0502]: cannot borrow `items` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let item = items.last();
  |                ----- immutable borrow occurs here
4 |     items.push(2);
  |     ^^^^^ mutable borrow occurs here
5 | }
  | - immutable borrow ends here

这段代码展示了Rust设计的确切场景。你有一个指向向量的引用,并且试图向向量中插入元素。这样做可能需要重新分配向量的内存,从而使任何现有的引用无效。如果发生这种情况,并且你在`item`中使用了该值,那么你将访问未初始化的内存,可能导致崩溃。
在这种特定情况下,你实际上并没有使用`item`(或原始代码中的`source`),所以你可以选择不调用那行代码。我猜你之所以这样做是有原因的,所以你可以将这些引用包装在一个代码块中,这样它们在你再次尝试改变值之前就会消失。
fn main() {
    let mut items = vec![1];
    {
        let item = items.last();
    }
    items.push(2);
}

这个技巧在现代的Rust中已经不再需要,因为非词法生命周期已经被实现,但底层的限制仍然存在 - 当存在对同一对象的其他引用时,你不能拥有一个可变引用。这是《Rust编程语言》中涵盖的引用规则之一。一个修改后的示例仍然无法与NLL一起工作。
let mut items = vec![1];
let item = items.last();
items.push(2);
println!("{:?}", item);

在其他情况下,您可以复制或克隆向量中的值。该项将不再是一个引用,您可以根据需要修改向量。
fn main() {
    let mut items = vec![1];
    let item = items.last().cloned();
    items.push(2);
}

如果您的类型不可克隆,您可以将其转换为引用计数值(例如RcArc),然后可以对其进行克隆。您可能还需要使用内部可变性
struct NonClone;

use std::rc::Rc;

fn main() {
    let mut items = vec![Rc::new(NonClone)];
    let item = items.last().cloned();
    items.push(Rc::new(NonClone));
}

这个例子来自《Programming Rust》,非常相似。
不,不是的,因为它根本不使用引用。
另请参阅:
- [无法将`*x`作为可变引用借用,因为它也被作为不可变引用借用](link1) - [根据最后一个元素将某物推入向量](link2) - [为什么可变借用的生命周期不会在函数调用完成后结束?](link3) - [如何重新组织我的图形代码以避免“一次同时借用变量作为可变引用多次”的错误?](link4) - [为什么会出现“无法将x作为可变引用借用多次”的错误?](link5) - [为什么Rust要一次性将变量借用为可变引用多次?](link6)

为什么《Programming Rust》的例子跟这不一样?我不太明白。我原本认为也可以写成 (&mut q).push('D')(&q).pop,这样既可作为可变引用借用 q,又可作为不可变引用借用 q - Shulhi Sapli
好的,我现在明白了。stackoverflow.com/q/36565833/155423 这个链接回答了为什么。 - Shulhi Sapli
2
@SujitJoshi,你的意思不太明确,但如果是 fn last(self) -> Option<T> 或者 fn last(&mut self) -> Option<T>(也就是 pop),那么没问题,因为返回值不会受到进一步修改向量的影响而失效。 - Shepmaster
我猜测API一定已经改变了,现在last()默认返回一个值而不是一个引用? - devoured elysium
@devouredelysium slice::last 返回一个 Option<&T> 并且永远都是这样。不用深入挖掘,这个变化很可能是由于非词法生命周期引起的。 - Shepmaster
显示剩余2条评论

11
尝试将您的不可变借用放置在块 {...} 中。 这会在块结束后结束借用。
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn some_test() {
        let mut graph = mk_graph();

        graph.add_node("source".to_string());
        graph.add_node("destination".to_string());

        {
            let source = graph.get_node_by_value("source").unwrap();
            let dest = graph.get_node_by_value("destination").unwrap();
        }

        graph.add_node("destination".to_string());
    }
}

4

如果有其他人遇到这个问题,想要快速解决 - 使用克隆而不是引用。例如,我正在迭代此单元格列表并希望更改属性,因此首先复制该列表:

let created = self.cells
  .into_iter()
  .map(|c| {
    BoardCell {
      x: c.x,
      y: c.y,
      owner: c.owner,
      adjacency: c.adjacency.clone(),
    }
   })
   .collect::<Vec<BoardCell>>();

然后通过循环复制品修改原始值:

for c in created {
  self.cells[(c.x + c.y * self.size) as usize].adjacency[dir] = count;
}

使用 Vec<&BoardCell> 会导致错误。不确定这是否符合 Rust 的规范,但它可以工作。

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