Hey Rustaceans!
I'm pretty new to Rust, coming from a C++ background, and I'm scratching my head over a borrowing issue. I've got this Graph struct, and I'm trying to figure out the most idiomatic way to work with it without running into borrow checker issues.
Here's what I've got:
pub struct Graph<T> {
indices: Vec<usize>,
edges: Vec<usize>,
vertices: Vec<T>,
}
pub fn edges<T>(graph: &Graph<T>, vertex: usize, offset: usize) -> Result<&[usize], Error> {
let edge = graph.indices[vertex];
let size = graph.edges[edge];
if offset >= size {
return Err(Error::NoHandle);
}
Ok(&graph.edges[edge + header_element_offset() + offset..edge + size + header_element_offset()])
}
// Usage
let e2_edges = edges(&graph, e2, 0)?;
for &edge in e2_edges {
if edge == 0 {
get_mut(&mut graph, *edge) = "2.1 edited"
assert_eq!(*get(&graph, *edge), "2.1.edited");
}
}
The problem is, when I try to modify a vertex after getting the edges, the borrow checker throws a fit. I get that it's because I'm borrowing the whole Graph in the edges
function, even though I'm only using some fields.
I've found a workaround by making separate functions for each field:
pub fn edges_from_indices(indices: &Vec<usize>, edges: &Vec<usize>, vertex: usize, offset: usize) -> Result<&[usize], Error> {
// Similar implementation
}
// Usage
let e2_edges = edges_from_indices(&graph.indices, &graph.edges, e2, 0)?;
for edge in e2_edges {
match *edge {
0 =>{
//*graph.get_vertex_mut(*edge) = "2.2.edited";
*get_from_vertices_mut(&mut graph.vertices, *edge) = "2.2.edited";
assert_eq!(*get(&graph, *edge), "2.2.edited");
}
_ => continue,
}
}
}
But man, this feels super verbose. In C++, I'd just pass a reference to the whole Graph and be done with it.
So, my question is: what's the idiomatic Rust way to handle this? Is there a pattern that lets me pass the whole Graph into the function without running into borrow issues, but also without having to split every field into its own parameter?
I've thought about a few options like using RefCell for interior mutability (But that has performance implication), splitting the Graph structure, or even using unsafe code with raw pointers (which feels wrong coming from C++, ironically). But I'm not sure what's the best approach here.
Obviously the program in the background is the same in both cases, but in one case the borrow checker doesn't know that only some fields are being used and instead counts the whole &Graph as a reference
Any insights from you Rust veterans would be super appreciated! Thanks in advance!
Full code here. What I wrote in this thread is just a very shortened pseudo-code