I am writing a small program to calculate the critical path on a PERT diagram (https://en.wikipedia.org/wiki/Program_evaluation_and_review_technique) in rust.
I am storing Task
objects inside a hashmap. The hashmap is owned by an object called Pert
. Each Task
object owns two Vec<String>
objects, identifying the task's prerequisites and successors, and has an i32 to specify its duration. The tasks are created inside main.rs and added to the Pert object through an add
function.
task.rs:
pub struct Task {
name: String,
i32: duration,
followers: Vec<String>,
prerequisites: Vec<String>
// Additional fields, not relevant for the example
}
impl Task {
pub fn new(name: &str, duration: i32) -> Task {
Task {
name: String::from(name),
duration: duration,
followers: Vec::new(),
prerequisites: Vec::new(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn duration(&self) -> i32 {
self.duration
}
pub fn get_prerequisites(&self) -> & Vec<String> {
&self.prerequisites
}
pub fn get_followers(&self) -> & Vec<String> {
&self.followers
}
}
To evaluate the critical path it is necessary to calculate the max sum of duration of all tasks and record the earliest start and finish times of each task, as well as the latest start and finish times. The way that can be done is to add a "begin" and an "end" task that mark the beginning and the end to the graph respectively. Starting from the "begin" task, a BFS is performed on the graph until we get to the "end" task. The BFS is done inside the method completion_time
of the Pert
object.
In my current implementation I hit a problem with the borrow checker, since I am borrowing mutably the hashmap containing the tasks more than once. I don't see an alternative way to do this other than borrowing twice, but I am very new to rust and have no functional programming experience, so if there is an easy way to do this with functional programming, I can't see it either.
pert.rs:
pub struct Pert {
tasks: HashMap<String, Task>
}
impl Pert {
pub fn completion_time(&mut self) -> i32 {
let mut time = 0;
let mut q = VecDeque::<&mut Task>::new();
// put "begin" task at the top of the queue, first mutable borrow of self.tasks
q.push_back(self.tasks.get_mut("begin").unwrap());
while !q.is_empty() {
let old_time = time;
let mut curr_task = q.pop_front().unwrap();
for x in curr_task.get_followers() {
// second mutable borrow of self.tasks happens here
let task = self.tasks.get_mut(x).unwrap();
// additional piece of code here modifying other task properties
time = std::cmp::max(old_time, old_time + task.duration())
}
}
time
}
}
Building the project with an empty main.rs should be enough to trigger the following error message:
error[E0499]: cannot borrow `self.tasks` as mutable more than once at a time
--> src/pert.rs:84:28
|
79 | q.push_back(self.tasks.get_mut("begin").unwrap());
| ---------- first mutable borrow occurs here
80 | while !q.is_empty() {
| - first borrow later used here
...
84 | let task = self.tasks.get_mut(x).unwrap();
| ^^^^^^^^^^ second mutable borrow occurs here