Rust, need a mutable reference of Self inside iteration

Viewed 872

I have a Graph data structure in Rust:

type NodeIndex = usize;

struct Graph {
    nodes: Vec<NodeIndex>,
    edges: Vec<(NodeIndex, NodeIndex)>,
}

I want to iterate over all the nodes inside a function and call a function which mutates the graph using each node as an element, say:

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
    }
}

Which doesn't work, since I would have more than one mutable reference. I also cannot pass &self, since then I would have a mutable and an immutable reference. How is this handled in Rust?

1 Answers

Well, you indeed cannot do this. I may name two main approaches applicable in general and for you example in particularly

Split out borrowing

That way is probably the hardest and/or the slowest way among others. Just do what borrow-checker want: don't mix up mutable and immutable borrows. For your case that can be as simple as cloning the nodes in mutate_fn:

let nodes = self.nodes.clone();
for node in nodes {
    self.mutate_using_node(node);
}

It's hard to reason without much details, but I think that's the only way to do for that approach. If you're only changing the edges, for example like this:

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);
        }
    }
}

That you may handle it simply by uniting these functions:

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);
        }
    }
}

So in general, there's no ultimate step-by-step guide (except maybe copying) for splitting out the code. It do depends on the code semantics.

Interior mutability

That is RefCell is about. It basically handle a borrow checking rules in runtime, if those are broken you get a panic. For the case that looks like this:

use std::cell::RefCell;

type NodeIndex = usize;

struct Graph {
    nodes: RefCell<Vec<NodeIndex>>,
    edges: RefCell<Vec<(NodeIndex, NodeIndex)>>,
}

fn mutate_fn(&self) {
    for &node in self.nodes.borrow().iter() {
        self.mutate_using_node(node);
    }
}

fn mutate_using_node(&self, node: NodeIndex) { // <- notice immutable ref
    for e in self.edges.borrow_mut().iter_mut() {
        if e.0 == node {
            std::mem::swap(&mut e.0, &mut e.1);
        }
    }
}

Keep in mind, that RefCell is not Sync, so it cannot be shared between threads. For case with threads Mutex or RwLock is an alternative.

Related