Hi everyone!
I am struggling a bit getting my head over the lifetime/references system.
Here is my take on the last exercise on common collections from the rust book.
https://doc.rust-lang.org/book/ch08-03-hash-maps.html#summary
I am using an OOP approach, and I am focusing on the add_employee
method.
1st approach
use std::collections::HashMap;
pub struct Directory {
departments: HashMap<String, Vec<String>>,
}
impl Directory {
fn new() -> Directory {
Directory {
departments: HashMap::new(),
}
}
fn add_employee(&mut self, employee_name: &str, department_name: &str) {
let department = self
.departments
.entry(department_name.to_string())
.or_insert(Vec::new());
department.push(employee_name.to_string());
}
}
It compiles.
What I don't like with it, is that we always create new string: department_name.to_string()
, even if the entry already exist.
2nd approach
use std::collections::HashMap;
pub struct Directory {
departments: HashMap<String, Vec<String>>,
}
impl Directory {
fn new() -> Directory {
Directory {
departments: HashMap::new(),
}
}
fn add_employee(&mut self, employee_name: &str, department_name: &str) {
let department = self.get_department(department_name);
department.push(employee_name.to_string());
}
fn get_department(&mut self, department_name: &str) -> &mut Vec<String> {
if let Some(department) = self.departments.get_mut(department_name) {
return department;
}
self.departments.insert(department_name.to_string(), Vec::new());
match self.departments.get_mut(department_name) {
Some(department) => department,
None => panic!("Should never happen"),
}
}
}
It doesn't compile, I get the following errors:
error[E0499]: cannot borrow `self.departments` as mutable more than once at a time
--> src/test2.rs:24:5
|
19 | fn get_department(&mut self, department_name: &str) -> &mut Vec<String> {
| - let's call the lifetime of this reference `'1`
20 | if let Some(department) = self.departments.get_mut(department_name) {
| ---------------- first mutable borrow occurs here
21 | return department;
| ---------- returning this value requires that `self.departments` is borrowed for `'1`
...
24 | self.departments.insert(department_name.to_string(), Vec::new());
| ^^^^^^^^^^^^^^^^ second mutable borrow occurs here
error[E0499]: cannot borrow `self.departments` as mutable more than once at a time
--> src/test2.rs:25:11
|
19 | fn get_department(&mut self, department_name: &str) -> &mut Vec<String> {
| - let's call the lifetime of this reference `'1`
20 | if let Some(department) = self.departments.get_mut(department_name) {
| ---------------- first mutable borrow occurs here
21 | return department;
| ---------- returning this value requires that `self.departments` is borrowed for `'1`
...
25 | match self.departments.get_mut(department_name) {
| ^^^^^^^^^^^^^^^^ second mutable borrow occurs here
I don't understand why it doesn't compile, as after the lines:
if let Some(department) = self.departments.get_mut(department_name) {
return department;
}
the mutable reference should be dropped, thus it should be able to get mutably borrowed again.
In this approach, we only build a String if we have to insert it in the HashMap. So it's better.
What I don't like is that we check twice if the key is in the HashMap, while we know that it is there, since we just added it.
3rd approach
use std::collections::HashMap;
pub struct Directory {
departments: HashMap<String, Vec<String>>,
}
impl Directory {
fn new() -> Directory {
Directory {
departments: HashMap::new(),
}
}
fn add_employee(&mut self, employee_name: &str, department_name: &str) {
let department = self.get_department(department_name);
department.push(employee_name.to_string());
}
fn get_department(&mut self, department_name: &str) -> &mut Vec<String> {
if let Some(department) = self.departments.get_mut(department_name) {
return department;
}
let mut department = Vec::new();
let department_ref = &mut department;
let department_name = department_name.to_string();
self.departments.insert(department_name, department);
department_ref
}
}
It doesn't compile, I get the following errors:
error[E0499]: cannot borrow `self.departments` as mutable more than once at a time
--> src/test3.rs:27:5
|
19 | fn get_department(&mut self, department_name: &str) -> &mut Vec<String> {
| - let's call the lifetime of this reference `'1`
20 | if let Some(department) = self.departments.get_mut(department_name) {
| ---------------- first mutable borrow occurs here
21 | return department;
| ---------- returning this value requires that `self.departments` is borrowed for `'1`
...
27 | self.departments.insert(department_name, department);
| ^^^^^^^^^^^^^^^^ second mutable borrow occurs here
error[E0505]: cannot move out of `department` because it is borrowed
--> src/test3.rs:27:46
|
19 | fn get_department(&mut self, department_name: &str) -> &mut Vec<String> {
| - let's call the lifetime of this reference `'1`
...
25 | let department_ref = &mut department;
| --------------- borrow of `department` occurs here
26 | let department_name = department_name.to_string();
27 | self.departments.insert(department_name, department);
| ^^^^^^^^^^ move out of `department` occurs here
28 | department_ref
| -------------- returning this value requires that `department` is borrowed for `'1`
error[E0515]: cannot return value referencing local variable `department`
--> src/test3.rs:28:5
|
25 | let department_ref = &mut department;
| --------------- `department` is borrowed here
...
28 | department_ref
| ^^^^^^^^^^^^^^ returns a value referencing data owned by the current function
As I am moving the department
inside a structure that outlive the method, the mutable reference department_ref
should still be valid. Is there a way to express that in Rust?
This is the kind of implementation I am looking for, as we only check once for the key inside the HashMap, and we only build the String for the key if needed.
I would like to know if it is actually possible to do.
Thank you in advance for the help you can give!