【问题标题】:Rust can't insert different structs into hashmapRust 不能将不同的结构插入 hashmap
【发布时间】:2021-05-08 07:24:12
【问题描述】:

假设我有一个结构,我想将它散列成 2 个HashMaps,这样第一个拥有对它的引用,而第二个拥有它,就像这样:

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

我得到的错误是:

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 无关,为什么会发生这种情况?

【问题讨论】:

  • "这工作正常,符合预期。"实际上,我认为您应该对此感到惊讶。您正在引用一些局部变量,然后在引用仍然存在时将其移动到其他地方。我不确定你会期望这样做。

标签: rust reference borrow-checker ownership borrowing


【解决方案1】:

当您将person1 移动到first_name_table 时,会使存储在last_name_table 中的引用&amp;person1 无效,但如果您不再使用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(())
}

【讨论】:

    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-21
      • 2019-06-06
      • 2021-03-01
      • 1970-01-01
      相关资源
      最近更新 更多