4 ms·
> My examples code above may not be persuasive to experienced Rustaceans. They might argue that the snippets don't show there is any real ergonomic problem, bec
by duped 1y ago
> My examples code above may not be persuasive to experienced Rustaceans. They might argue that the snippets don't show there is any real ergonomic problem, because the solutions to make the snippets compile are completely trivial. In the last example, I could just derive Clone + Copy for Id.
No, you could use destructuring. This doesn't work for all cases but it does for your examples without needing to derive copy or clone. Here's a more complex but also compelling example of the problem:
struct Graph {
nodes: BTreeMap<u32, Node>,
}
struct Node {
edges: Vec<u32>,
}
impl Graph {
fn visit_mut(&mut self, visit: impl Fn(&mut Node, &mut Node)) {
let mut visited = BTreeSet::new();
let mut stack = vec![0];
while let Some(id) = stack.pop() {
if !visited.insert(id) { continue; }
let curr = self.nodes.get_mut(&id);
for id in source.edges.clone() {
let next = self.nodes.get_mut(&id);
visit(curr, next);
stack.push(id);
}
}
}
}
We're doing everything in the "Rust" way here. We're using IDs instead of pointers. We're cloning a vec even if it's a bit excessive. But the bigger problem is we actually _do_ need to have multiple mutable references to two values owned by a collection that we know don't transitively reference the collection. We need to wrap these in an RefCell or UnsafeCell and unsafe { } block to actually get mutable references to the underlying data to correctly implement visit_mut().
This is a problem that shows up all the time when using collections, which Rust encourages within the ecosystem.
- ethan_smith 1y agoYour example has an undefined 'source' variable - you likely meant 'curr' - but more importantly, this pattern can be solved with indices + interior mutability or split_at_mut() rather than unsafe, giving you safe simultaneous mutable access to different nodes.
- duped 1y agoThat requires you use a Vec or similar data structure which may not be appropriate/forces you to reinvent garbage collection or deal with tombstoning/generations/etc for unused slots. And I pointed out you can use interior mutability. Still sucks because the code is guaranteed sound, the compiler just can't prove it. IMO the correct choice is UnsafeCell and unsafe {}.
- oneshtein 1y agoWhat stops you from writing such data container with interior mutability, like vec_cell[0], which then enforce borrowing rules at runtime? [0]: https://github.com/alexanderved/vec_cell https://github.com/alexanderved/vec_cell