Reputation: 1648
I'm trying to implement Dijkstra's algorithm in Rust. I am getting an error because I'm mutating a HashMap
inside a match
expression on a value obtained from .get()
. I understand why this is a violation of the borrowing and mutation principles of Rust, but I haven't found a workaround.
I have tried using .entry()
and .and_modify()
to perform an in-place modification, but I also need to insert and/or modify other keys besides the one being matched on.
Are there any constructs/functions I can use to safely enable this mutation within a get
, or, failing that, is there another approach to this algorithm that steers clear of this issue?
(Unfinished) Code Snippet:
use std::collections::{HashMap, HashSet};
struct Graph {
vertices: Vec<i32>,
edges: HashMap<i32, HashMap<i32, i32>>, // start -> {end -> weight}
}
fn dijkstra(start: i32, g: Graph) -> HashMap<i32, HashMap<i32, (i32, Vec<i32>)>> {
let mut known_paths: HashMap<i32, (i32, Vec<i32>)> = HashMap::new(); // end -> (total_weight, path)
known_paths.insert(start, (0, Vec::new()));
let mut current = &start;
let mut unvisited: HashSet<i32> = g.edges.keys().cloned().collect();
while unvisited.len() > 0 {
match known_paths.get(current) {
Some((current_dist, current_path)) => if let Some(ref incident_edges) =
g.edges.get(current)
{
for (neighbor, dist) in incident_edges.iter() {
match known_paths.get(&neighbor) {
Some((old_distance, _old_path)) => if current_dist + dist < *old_distance {
let mut new_path = current_path.clone();
new_path.push(*current);
known_paths.insert(*neighbor, (current_dist + dist, new_path));
},
None => {
let mut new_path = current_path.clone();
new_path.push(*current);
known_paths.insert(*neighbor, (current_dist + dist, new_path));
}
}
}
},
None => panic!("Something went wrong with current={}", current),
}
}
HashMap::new()
}
Error:
error[E0502]: cannot borrow `known_paths` as mutable because it is also borrowed as immutable
--> src/lib.rs:26:29
|
17 | match known_paths.get(current) {
| ----------- immutable borrow occurs here
...
26 | known_paths.insert(*neighbor, (current_dist + dist, new_path));
| ^^^^^^^^^^^ mutable borrow occurs here
...
37 | }
| - immutable borrow ends here
error[E0502]: cannot borrow `known_paths` as mutable because it is also borrowed as immutable
--> src/lib.rs:31:29
|
17 | match known_paths.get(current) {
| ----------- immutable borrow occurs here
...
31 | known_paths.insert(*neighbor, (current_dist + dist, new_path));
| ^^^^^^^^^^^ mutable borrow occurs here
...
37 | }
| - immutable borrow ends here
Upvotes: 1
Views: 137
Reputation: 300099
No.
I am getting an error because I'm mutating a
HashMap
inside a match expression on a value obtained from.get()
. I understand why this is a violation of the borrowing and mutation principles of Rust, but I haven't found a workaround.
I am afraid you have not actually understood the borrowing principles.
The key principle which underpins Rust's safety is: Mutation NAND Aliasing.
This principle is then enforced either at run-time or at compile-time, with a strong preference for compile-time when feasible since it is free of run-time overhead.
In your situation, you have:
HashMap
, obtained from HashMap::get()
.HashMap
while holding onto the reference.Let's imagine, for a second, that some constructs allows this code to compile and run; what would happen? BOOM.
When inserting an element in the HashMap
, the HashMap
may:
Therefore, HashMap::insert
means that any existing reference to an element of the HashMap
may become dangling and point to either another element, or random memory.
There is no safe way to insert into the HashMap
while holding a reference to an element of the HashMap
.
You need to find a solution which does not involve keeping a reference to the HashMap
.
My advice would be to simply clone
: match known_paths.get(current).clone()
. Once you have a first working implementation of the algorithm, you can look into possibly improving its performance, as necessity dictates.
Upvotes: 2