< 返回版块

Zhanghailin1995 发表于 2020-09-01 18:10

Tags:rust,nll

知乎上一篇2018年初关于nll的文章,当时nll还未进stable。大致意思是在nll情况下这个代码可以通过编译,以前看的时候没有测试过,今天试了一下是编译不过的,应该如何更正一下?

use std::collections::HashMap;

fn main() {
    let map = &mut HashMap::new();
    map.insert(22, format!("Hello, world"));
    map.insert(44, format!("Goodbye, world"));
    assert_eq!(&*get_default(map, 22), "Hello, world");
    assert_eq!(&*get_default(map, 66), "");

}

fn get_default(map: &mut HashMap<usize, String>, key: usize) -> &mut String {
    match map.get_mut(&key) {
        Some(value) => value,
        None => {
            map.insert(key, "".to_string());
            map.get_mut(&key).unwrap()
        }
    }
}

报错如下

error[E0499]: cannot borrow `*map` as mutable more than once at a time
  --> examples\nll_test.rs:38:13
   |
34 |   fn get_default(map: &mut HashMap<usize, String>, key: usize) -> &mut String {
   |                       - let's call the lifetime of this reference `'1`
35 |       match map.get_mut(&key) {
   |       -     --- first mutable borrow occurs here
   |  _____|
   | |
36 | |         Some(value) => value,
37 | |         None => {
38 | |             map.insert(key, "".to_string());
   | |             ^^^ second mutable borrow occurs here
39 | |             map.get_mut(&key).unwrap()
40 | |         }
41 | |     }
   | |_____- returning this value requires that `*map` is borrowed for `'1`

Ext Link: https://zhuanlan.zhihu.com/p/32884290

评论区

写评论
xjkdev 2020-09-03 02:43

我重新思考了一下,觉得我之前应该说错了,这段代码应该不是双重可变引用的问题。但是貌似应该不是nll能解决的。应该是当前borrow checker设计上就不能解决的问题,需要下一代borrow checker。

这个问题应该和这个 issue#47680 https://github.com/rust-lang/rust/issues/47680 是一样的,在 http://smallcultfollowing.com/babysteps/blog/2018/04/27/an-alias-based-formulation-of-the-borrow-checker/这篇文章中提到下一代borrow checker代号polonius也许才能解决。

whfuyn 2020-09-02 22:28
use std::collections::HashMap;

fn main() {
    let map = &mut HashMap::new();
    map.insert(22, format!("Hello, world"));
    map.insert(44, format!("Goodbye, world"));
    assert_eq!(&*get_default(map, 22), "Hello, world");
    assert_eq!(&*get_default(map, 66), "");

}

fn get_default(map: &mut HashMap<usize, String>, key: usize) -> &mut String {
    match map.get_mut(&key) {
        Some(_value) => panic!(),
        None => {
            map.insert(key, "".to_string());
            map.get_mut(&key).unwrap()
        }
    }
}

Playground

我猜这里是因为Some(value) => value,这个分支用到了map.get_mut(&key)的值,导致对*map的可变引用无法在进入分支之前结束,把返回value这个去掉就可以通过编译了。

作者 Zhanghailin1995 2020-09-02 09:22

谢谢!

--
👇
xjkdev: 我觉得文章的例子应该也是举错了。

xjkdev 2020-09-01 20:39

我觉得文章的例子应该也是举错了。

xjkdev 2020-09-01 20:34

我觉得这不是nll的问题,nll解决的是能通过手动加花括号就能解决的生命周期问题,而你这个不是nll能解决的声明周期问题。

我觉得问题是因为你参数先&mut借用了map,但是结尾又返回了从&mut map里派生的&mut String。在调用者的位置,就相当于&mut map这个对象同时有两个可变引用了,这个是lifetime规则不允许的——一个对象同时存在两个可变引用。

get_default这样的功能可以用HashMap的entry机制来做。

map.entry(key).or_default().and_modify(|value|...);
1 共 5 条评论, 1 页