Rust,在迭代中需要一个可变的 Self 引用

eag*_*arn 6 rust borrow-checker

我在 Rust 中有一个 Graph 数据结构:

type NodeIndex = usize;

struct Graph {
    nodes: Vec<NodeIndex>,
    edges: Vec<(NodeIndex, NodeIndex)>,
}
Run Code Online (Sandbox Code Playgroud)

我想遍历函数内的所有节点并调用一个函数,该函数使用每个节点作为元素来改变图形,例如:

impl Graph {
    fn mutate_fn(&mut self) {
        for node in self.nodes {
            self.mutate_using_node(node);
        }
    }

    fn mutate_using_node(&mut self, node: NodeIndex) {
        // mutate self here
    }
}
Run Code Online (Sandbox Code Playgroud)

这是行不通的,因为我会有不止一个可变引用。我也不能通过 &self,因为那样我就会有一个可变引用和一个不可变引用。这在 Rust 中是如何处理的?

Kit*_*tsu 1

嗯,你确实不能这样做。我可以列举两种普遍适用的主要方法,特别适合您的例子

拆分借款

这种方式可能是其他方式中最难和/或最慢的方式。只需执行借用检查器想要的操作即可:不要混淆可变借用和不可变借用。对于您的情况,这可以像克隆以下节点一样简单mutate_fn

let nodes = self.nodes.clone();
for node in nodes {
    self.mutate_using_node(node);
}
Run Code Online (Sandbox Code Playgroud)

如果没有太多细节,很难推理,但我认为这是实现该方法的唯一方法。如果您只更改边缘,例如如下所示:

fn mutate_using_node(&mut self, node: NodeIndex) {
    for e in &mut self.edges {
        if e.0 == node {
            std::mem::swap(&mut e.0, &mut e.1);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以简单地通过组合这些函数来处理它:

fn mutate_using_node(&mut self, node: NodeIndex) {
    for e in &mut self.edges {
        if e.0 == node {
            std::mem::swap(&mut e.0, &mut e.1);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,一般来说,没有最终的分步指南(除了复制)来拆分代码。它确实取决于代码语义。

内部可变性

那是RefCell关于。它基本上在运行时处理借用检查规则,如果这些规则被破坏,你会感到恐慌。对于如下所示的情况:

for node in self.nodes.iter().copied() {
    for e in &mut self.edges {
        if e.0 == node {
            std::mem::swap(&mut e.0, &mut e.1);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请记住,这RefCell不是Sync,因此它不能在线程之间共享。对于有螺纹的情况Mutex, 或者RwLock是一种替代方案。