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