【问题标题】:How can multiple parts of self be borrowed here? Isn't self borrowed mutably as well as immutably here?如何在这里借用自我的多个部分? self 在这里借用的不是可变的和不变的吗?
【发布时间】:2020-12-19 20:44:57
【问题描述】:

我有这个结构:

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 显然先是作为不可变的,然后是可变的,这正如预期的那样失败了。

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

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

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

为什么我在使用第一个版本时没有收到此错误?

如果您想知道:

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

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

谢谢!

【问题讨论】:

    标签: rust lifetime self borrow-checker borrowing


    【解决方案1】:

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

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

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

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


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

    【讨论】:

    • 这很有意义。我知道不支持部分借用,所以我很困惑第一个版本似乎支持它们,但签名参数是有道理的。我想我什至很高兴它们不受支持,因为这对我来说似乎很容易解决,而且学习的语法更少总是一件好事。如果有人有类似的问题并且对如何查看提到的 RFC 中的 this comment 以及那里的其他 cmets 感兴趣。
    【解决方案2】:

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

    当你从 &amp;self.edges[edge_index] 借用时,Rust 可以知道不可变引用的生命周期专门与 self.edges 绑定,当你借用 self.nodes[node_index].remove_edge(edge_index) 时,Rust 可以知道可变引用的生命周期专门绑定到 self.nodes 并且因为 self.edgesself.nodes 是不相交的结构字段,引用之间没有重叠,它们可以同时存在。

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

    【讨论】:

    • 我接受了另一个答案,因为对拆分借用的引用以及 RFC 对我很有启发性,但我只是想让你知道我很难做出决定,因为你的答案非常详细而且内容丰富。谢谢。
    猜你喜欢
    • 2018-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-02
    相关资源
    最近更新 更多