无法将`graph`借为不可变的,因为它也被借为可变的。如何让 Rust 知道我已经完成了可变借用?

3

我正在尝试在Rust中创建一个类似图形的结构。我的第一次实现编译得很好:

fn main() {
    let mut graph: Graph = Graph::new(); // Contains a vector of all nodes added to the graph.  The graph owns the nodes.

    // Create a node
    let parent: usize = graph.add_node(ParentNode::new()); // Returns the ID of the node.
    let parent: &Node = graph.get_node_with_id(parent); // Returns a borrowed reference to the node with the given ID

    // Print the number of nodes
    println!("Num nodes: {}", graph.count_nodes());
}

我不喜欢先调用add_node,再调用get_node_with_id,所以我写了另一个方法将这两个步骤合并成一个:

fn main() {
    let mut graph: Graph = Graph::new();

    // Create a node
    let parent: &Node = graph.add_node_and_borrow(ParentNode::new());

    // Print the number of nodes
    println!("Num nodes: {}", graph.count_nodes());
}

add_node_and_borrow 只是一种简写方式:

/// Like add_node, but returns a borrowed reference
/// instead of the id
pub fn add_node_and_borrow(&mut self, node: Box<Node>) -> &Node {
    let id = self.add_node(node);
    return self.get_node_with_id(id);
}

当我尝试编译这个时,出现了一个错误:
error[E0502]: cannot borrow `graph` as immutable because it is also borrowed as mutable
  --> src/main.rs:23:31
   |
20 |     let parent: &Node = graph.add_node_and_borrow(ParentNode::new());
   |                         ----- mutable borrow occurs here
...
23 |     println!("Num nodes: {}", graph.count_nodes());
   |                               ^^^^^ immutable borrow occurs here
24 | }
   | - mutable borrow ends here

奇怪!在这两个例子中,我执行的是完全相同的操作,是吗?为什么Rust认为我在第二个例子中从未停止可变地借用graph

这是完整的源文件减去无关紧要的部分,这样您就可以看到整个情况:

fn main() {
    does_not_compike();
}

fn compiles() {
    let mut graph: Graph = Graph::new();

    // Create a node
    let parent: usize = graph.add_node(ParentNode::new());
    let parent: &Node = graph.get_node_with_id(parent);

    // Print the number of nodes
    println!("Num nodes: {}", graph.count_nodes());
}

fn does_not_compike() {
    let mut graph: Graph = Graph::new();

    // Create a node
    let parent: &Node = graph.add_node_and_borrow(ParentNode::new());

    // Print the number of nodes
    println!("Num nodes: {}", graph.count_nodes());
}

struct Graph {
    nodes: Vec<Box<Node>>,
    next_node_id: usize,
}

impl Graph {
    pub fn new() -> Graph {
        // Construct a new graph with no nodes.
        let new_graph = Graph {
            nodes: Vec::new(),
            next_node_id: 0,
        };

        return new_graph;
    }

    /// Adds a newly-created node to graph.
    /// The graph becomes the new owner of the node.
    /// Returns the node id of the node.
    pub fn add_node(&mut self, node: Box<Node>) -> usize {
        // Add the node
        self.nodes.push(node);

        // Return the id
        let id = self.next_node_id;
        self.next_node_id += 1;

        return id;
    }

    /// Like add_node, but returns a borrowed reference
    /// instead of the id
    pub fn add_node_and_borrow(&mut self, node: Box<Node>) -> &Node {
        let id = self.add_node(node);
        return self.get_node_with_id(id);
    }

    /// Returns a borrowed reference to the node with the given id
    pub fn get_node_with_id(&self, id: usize) -> &Node {
        return &*self.nodes[id];
    }

    pub fn count_nodes(&self) -> usize {
        return self.nodes.len();
    }
}

trait Node {
    // Not important
}

struct ParentNode {
    // Not important
}

impl ParentNode {
    pub fn new() -> Box<Node> {
        Box::new(ParentNode {
            // lol empty struct
        })
    }
}

impl Node for ParentNode {
    // Not important
}

我正在做完全相同的事情...不是吗? - 不是的。区别在于get_node_with_id采用不可变引用&self,而add_node_and_borrow采用可变引用&mut self。(即使在第一个示例中,您也无法在获取第一个节点后添加第二个节点...) - MB-F
如何让Rust知道我已经完成了可变借用?将let parent: &Node = graph.add_node_and_borrow(ParentNode::new());放在花括号中。 - Shepmaster
@Shepmaster 但这样我就无法在后面使用parent了。我计划创建一个名为“ChildNode”的新节点类型,它持有对其父节点的借用引用。为了构造ChildNode,我需要保持parent在作用域内,以便将其作为参数传递。 - user2102929
@user2102929,没错。你不能同时保留parent并继续修改graph,因为修改graph可能会使引用无效。此时访问parent将读取未定义的内存,导致崩溃或安全漏洞。 - Shepmaster
1个回答

1
在您的第一个示例中:
let parent: usize = graph.add_node(ParentNode::new());

在调用 add_node 时,可变地借用了 graph,然后释放了这个借用。

let parent: &Node = graph.get_node_with_id(parent);

在第二个例子中,它不可变地借用了graph并且保留了这个借用,因为父节点是属于图形的一个节点的引用。此时,正如 @kazemakase所说, 你将无法向图中添加新节点,因为不可变借用会阻止你创建新的可变借用。但是,你可以重新以不可变方式借用graph来打印它。

在您的第二个示例中:

let parent: &Node = graph.add_node_and_borrow(ParentNode::new());

函数签名要求可变借用graph,并且由于parent引用的存在,保持了这个借用。因此,在这之后,你将无法重新借用图形,因为你已经有一个活跃的可变借用。


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