获取 HashMap 条目,如果没有则添加它

bil*_*rer 1 hashmap rust borrow-checker

我想做这样的事情:

fn some_fn() {
    let mut my_map = HashMap::from([
        (1, "1.0".to_string()),
        (2, "2.0".to_string()),
    ]);

    let key = 3;

    let res = match my_map.get(&key) {
        Some(child) => child,
        None => {
            let value = "3.0".to_string();
            my_map.insert(key, value);
            &value // HERE IT FAILS
        }
    };

    println!("{}", res);
}
Run Code Online (Sandbox Code Playgroud)

但编译时出现错误:

error[E0597]: `value` does not live long enough
  --> src/lib.rs:16:13
   |
16 |             &value // HERE IT FAILS
   |             ^^^^^^
   |             |
   |             borrowed value does not live long enough
   |             borrow later used here
17 |         }
   |         - `value` dropped here while still borrowed

error[E0382]: borrow of moved value: `value`
  --> src/lib.rs:16:13
   |
14 |             let value = "3.0".to_string();
   |                 ----- move occurs because `value` has type `String`, which does not implement the `Copy` trait
15 |             my_map.insert(key, value);
   |                                ----- value moved here
16 |             &value // HERE IT FAILS
   |             ^^^^^^ value borrowed here after move
Run Code Online (Sandbox Code Playgroud)

操场

我怎样才能优雅地修复它?在我看来,复制字符串似乎不是最佳选择。

Apl*_*123 5

这里的问题是,您插入value字符串然后尝试返回对它的引用,尽管它已经被移动到 HashMap 中。您真正想要的是插入值,然后获取对插入值的引用,这可能如下所示:

let res = match my_map.get(&key) {
    Some(child) => child,
    None => {
        let value = "3.0".to_string();
        my_map.insert(key, value);
        my_map.get(&key).unwrap() // unwrap is guaranteed to work
    }
};
Run Code Online (Sandbox Code Playgroud)

但不要这样做。它丑陋且缓慢,因为它必须在地图中查找密钥两次。Rust 有一个方便的Entry类型,可以让你获取 a 的条目HashMap,然后对其执行操作:

// or_insert returns the value if it exists, otherwise it inserts a default and returns it
let res = my_map.entry(key).or_insert("3.0".to_string());
Run Code Online (Sandbox Code Playgroud)

或者,如果生成默认值的操作成本很高,您可以使用or_insert_with传递闭包:

// this won't call "3.0".to_string() unless it needs to
let res = my_map.entry(key).or_insert_with(|| "3.0".to_string());
Run Code Online (Sandbox Code Playgroud)