问题描述
我有这个结构:
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.edges
和 self.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.nodes
和 self.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 self
和 self
借用的非重叠引用),因此它会引发编译错误。