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?
Well, you indeed cannot do this. I may name two main approaches applicable in general and for you example in particularly
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With