Reputation: 1
Suppose I have a struct and I want to hash it into 2 HashMap
s, such that the first one holds a reference to it and the second one owns it, like that:
struct Person { id: i32 }
fn main() -> std::io::Result<()> {
let mut first_name_table = HashMap::new();
let mut last_name_table = HashMap::new();
let person1 = Person { id: 1};
let first_name1 = "first1";
let last_name1 = "last1";
last_name_table.insert(last_name1, &person1);
first_name_table.insert(first_name1, person1);
Ok(())
}
This works fine and as expected. But, when I try to insert a second person, the borrow checker freaks out:
struct Person { id: i32 }
fn main() -> std::io::Result<()> {
let mut first_name_table = HashMap::new();
let mut last_name_table = HashMap::new();
let person1 = Person { id: 1};
let first_name1 = "first1";
let last_name1 = "last1";
last_name_table.insert(last_name1, &person1);
first_name_table.insert(first_name1, person1);
let person2 = Person { id: 2};
let first_name2 = "first2";
let last_name2 = "last2";
last_name_table.insert(last_name2, &person2);
first_name_table.insert(first_name2, person2);
Ok(())
}
The error I'm getting is:
error[E0505]: cannot move out of `person1` because it is borrowed
--> src/main.rs:20:42
|
19 | last_name_table.insert(last_name1, &person1);
| -------- borrow of `person1` occurs here
20 | first_name_table.insert(first_name1, person1);
| ^^^^^^^ move out of `person1` occurs here
...
26 | last_name_table.insert(last_name2, &person2);
| --------------- borrow later used here
But line 26 has nothing to do with person1
so why does this happen?
Upvotes: 0
Views: 196
Reputation: 60522
I'm surprised the first version compiles at all, considering you store a reference to the person and then immediately move it. I guess the compiler can see that the reference isn't used beyond that point (edit: this is because of #[may_dangle]
within the map's Drop
implementation). Regardless, its not quite the second insert but any use of last_name_table
will trigger an error:
let mut first_name_table = HashMap::new();
let mut last_name_table = HashMap::new();
let person1 = Person { id: 1};
let first_name1 = "first1";
let last_name1 = "last1";
last_name_table.insert(last_name1, &person1);
first_name_table.insert(first_name1, person1);
println!("{:?}", last_name_table);
error[E0505]: cannot move out of `person1` because it is borrowed
--> src/main.rs:15:42
|
14 | last_name_table.insert(last_name1, &person1);
| -------- borrow of `person1` occurs here
15 | first_name_table.insert(first_name1, person1);
| ^^^^^^^ move out of `person1` occurs here
16 |
17 | println!("{:?}", last_name_table);
| --------------- borrow later used here
You could try to insert it into one then get the reference to avoid the move issue:
let person1_ref = first_name_table.entry(first_name1).or_insert(person1);
last_name_table.insert(last_name1, person1_ref);
But that won't let you modify first_name_table
any more since last_name_table
is immutably referencing it. Almost any operation on hash maps could end up moving existing elements meaning the reference would become invalid. Rust will prevent you from doing this.
The fix is to clear up your ownership model. I'd recommend using Rc
such that each map shares ownership of the person. See it working on the playground:
let mut first_name_table = HashMap::new();
let mut last_name_table = HashMap::new();
let person1 = Rc::new(Person { id: 1});
let first_name1 = "first1";
let last_name1 = "last1";
last_name_table.insert(last_name1, Rc::clone(&person1));
first_name_table.insert(first_name1, person1);
let person2 = Rc::new(Person { id: 2});
let first_name2 = "first2";
let last_name2 = "last2";
last_name_table.insert(last_name2, Rc::clone(&person2));
first_name_table.insert(first_name2, person2);
Upvotes: 2
Reputation: 15155
When you move person1
into first_name_table
you invalidate the reference &person1
stored in last_name_table
but if you never use last_name_table
again then the compiler lets the code compile, but as soon as you attempt to use last_name_table
the compile will throw an error because it contains an invalid reference. It doesn't matter how or when you try to use it. Even simply dropping it will also trigger the error:
use std::collections::HashMap;
struct Person { id: i32 }
fn main() -> std::io::Result<()> {
let mut first_name_table = HashMap::new();
let mut last_name_table = HashMap::new();
let person1 = Person { id: 1};
let first_name1 = "first1";
let last_name1 = "last1";
last_name_table.insert(last_name1, &person1);
first_name_table.insert(first_name1, person1);
drop(last_name_table); // triggers error
Ok(())
}
Upvotes: 2