在这里如何借用self的多个部分?这里的 self 借用不是可变和不变的吗?

问题描述

我有这个结构:

struct Physicsstate {
    nodes: Vec<Node>,edges: Vec<Edge>,}

我试图理解为什么这段代码会编译:

impl Physicsstate {
    fn remove_edge(&mut self,edge_index: usize) {
        let edge = &self.edges[edge_index];    // first borrow here
        // update the edge-index collection of the nodes connected by this edge
        for i in 0..2 {
            let node_index = edge.node_indices[i];
            self.nodes[node_index].remove_edge(edge_index);   // second (mutable) borrow here ?
        }
    }
}

虽然失败了:

impl Physicsstate {
    pub fn edge_at(&self,edge_index: usize) -> &Edge {
        &self.edges[edge_index]
    }

    pub fn node_at_mut(&mut self,node_index: usize) -> &mut Node {
        &mut self.nodes[node_index]
    }

    fn remove_edge(&mut self,edge_index: usize) {
        let edge = self.edge_at(edge_index);    // first (immutable) borrow here
        for i in 0..2 {
                let node_index = edge.node_indices[i];
                self.node_at_mut(node_index).remove_edge(edge_index);   // second (mutable) borrow here -> ERROR
            }
        }
    }
}

我原本用的是第一个版本,后来改成第二个,结果发现失败了。 对我来说它失败是有道理的。 self 显然首先被借为不可变,然后是可变,正如预期的那样失败。

我不明白的是:第一个版本是如何工作的?

显然,第一次借用(获得 &Edge)必须在整个 for 循环中保持活动状态,因为它在那里使用。但是它如何设法从 Node 获得对 self 的额外可变引用?

第二个版本的编译器返回的错误是:error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable

为什么我在使用第一个版本时没有出现这个错误

以防万一:

  • Node 和 Edge 是简单的结构,没有实现 copy,所以这不是这里发生的事情
  • 我想切换到第二个版本的原因是那里的两个附加函数实际上包含要使用的类型转换,为了可读性,我在这里删除了它们,但是我必须在我的代码中到处重复没有这些功能

也许我可以改用宏来实现相同的效果,但总的来说,我只想知道这里的借用是如何工作的,因为在我看来,我对此有某种误解。

谢谢!

解决方法

您可以在第一个版本中借用 self.edges 和随后的 self.nodes 的原因是因为编译器明白 self.edgesself.nodes 分别是被借用的。这也是与结构相关的所谓“Splitting Borrows”。

但是,如果您不透明地查看方法签名:

fn edge_at(&self,edge_index: usize) -> &Edge

那么看那个,你知道借的是什么吗?并不真地。您只能看到它返回 &Edge 并且 &self 正在被借用。因此 self 作为一个整体是被借用的,这不允许您对 self.nodes 进行后续的可变借用,因为 self 已经被不可改变地借用了。


您本质上希望发生的是调用方法允许部分借用 &self。这在 Rust 中不受支持。但是,有一个可追溯到 2015 年的 RFC 要求此功能。 RFC 的标题是“Partial Borrowing (#1215)”,它讨论了潜在的语法和语义。

,

第一个版本有效,因为在单个范围内,Rust 借用检查器可以“看到”更细粒度的借用,因此它可以判断何时从结构的不相交字段中借用了引用,就像在您的示例中一样。

当您从 &self.edges[edge_index] 借用时,Rust 可以判断不可变引用的生命周期与 self.edges 相关,而当您借用 self.nodes[node_index].remove_edge(edge_index) 时,Rust 可以判断可变引用的生命周期与 {{ 1}} 并且由于 self.nodesself.edges 是不相交的结构字段,因此引用之间没有重叠,它们可以同时存在。

第二个例子失败了,因为你将单个作用域分解成多个作用域,现在 Rust 借用检查器只能根据你通过方法签名通知它的内容来推断引用的生命周期,而第一个方法签名 {{1} } 表示返回引用的生命周期与 self.nodes 相关联,第二个方法签名 fn edge_at(&self,edge_index: usize) -> &Edge 表示返回引用的生命周期与 &self 相关联,现在当您在单个范围内使用这两种方法时Rust 认为您从 fn node_at_mut(&mut self,node_index: usize) -> &mut Node 借用了相互冲突的重叠引用(而不是像以前一样从 &mut selfself 借用的非重叠引用),因此它会引发编译错误。