nikos-alexandris
nikos-alexandris

Reputation: 1

Rust can't insert different structs into hashmap

Suppose I have a struct and I want to hash it into 2 HashMaps, 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

Answers (2)

kmdreko
kmdreko

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

pretzelhammer
pretzelhammer

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

Related Questions