Rust 不能在 hashmap 中插入不同的结构

问题描述

假设我有一个结构体,我想将它散列到 2 个 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);

    Ok(())
}

这工作正常并且符合预期。但是,当我尝试插入第二个人时,借用检查器吓坏了:

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);
    
    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(())
}

我得到的错误是:

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

但第 26 行与 person1 无关,为什么会发生这种情况?

解决方法

当您将 person1 移入 first_name_table 时,您会使存储在 &person1 中的引用 last_name_table 无效,但如果您不再使用 last_name_table,那么编译器会让代码编译,但是一旦您尝试使用 last_name_table,编译就会抛出错误,因为它包含无效的引用。您如何或何时尝试使用它并不重要。即使只是简单地删除它也会触发错误:

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(())
}
,

我很惊讶第一个版本竟然可以编译,因为您存储了对这个人的引用,然后立即移动了它。我猜编译器可以看到在该点之后没有使用引用(编辑:这是因为地图的 #[may_dangle] 实现中的 Drop )。无论如何,这不是第二次插入,但任何使用 last_name_table 都会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

可以尝试将其插入一个然后获取引用以避免移动问题:

let person1_ref = first_name_table.entry(first_name1).or_insert(person1);
last_name_table.insert(last_name1,person1_ref);

但这不会让您再修改 first_name_table,因为 last_name_table 一直在引用它。几乎所有对哈希映射的操作都可能最终移动现有元素,这意味着引用将变得无效。 Rust 会阻止你这样做。

解决方法是清除您的所有权模型。我建议使用 Rc,这样每个地图共享该人的所有权。看看它在 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);