为什么HashMap :: get_mut比HashMap更挑剔:: get关于生命周期?

Pie*_*ine 4 lifetime rust

我有一个参考struct Foo<'a>包装&'a str.我想HashMapFoos作为键来填充.这是一段代码(在操场上打开):

use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash)]
struct Foo<'a> {
    txt: &'a str,
}

fn main() {
    let a = "hello".to_string();
    let a2 = Foo { txt: &a };
    let b = "hello".to_string();
    let b2 = Foo { txt: &b };

    let mut hm = HashMap::<Foo, u32>::new();

    hm.insert(a2, 42);
    println!("=== {:?}", hm.get(&b2));     // prints Some(42)
    println!("=== {:?}", hm.get_mut(&b2)); // prints Some(42)

    {
        let c = "hello".to_string();
        let c2 = Foo { txt: &c };
        println!("=== {:?}", hm.get(&c2));         // prints Some(42)
        // println!("=== {:?}", hm.get_mut(&c2));  // does not compile. Why?
        // hm.insert(c2, 101);                     // does not compile, but I understand why.
    }
}
Run Code Online (Sandbox Code Playgroud)

这段代码编译和运行完美,但编译器抱怨我是否取消注释最后两行代码.更确切地说,它抱怨借来的价值c2不够长.

对于最后一个(insert),这是完全可以理解的:我不能移动c2HashMap,这生活比借用数据不再c2c.

但是,我不明白为什么倒数第二行(get_mut)有同样的问题:在这种情况下,借用的数据应该只在调用期间是必需的get_mut,它不会被移入HashMap.

这是更加令人惊讶的是,get上述工程完美(如我所料),以及两个getget_mut具有相同签名时涉及到k参数...


在挖掘了一点之后,我用普通引用(而不是嵌入引用的结构)重现了这个问题.

use std::collections::HashMap;

fn main() {
    let a = 42;
    let b = 42;

    let mut hm = HashMap::<&u32,u32>::new();

    hm.insert(&a, 13);
    println!("=== {:?}", hm.get(&&b));     // prints Some(13)
    println!("=== {:?}", hm.get_mut(&&b)); // prints Some(13)

    {
        let c = 42;
        println!("=== {:?}", hm.get(&&c));        // prints Some(13)
        //println!("=== {:?}", hm.get_mut(&&c));  // does not compile. Why?
    } 
}
Run Code Online (Sandbox Code Playgroud)

(在操场上开放)

同样,取消注释最后一行会导致编译器抱怨(与上面相同的消息).

然而,我发现了一个有趣的办法解决这个具体的例子:更换&&c通过&c在最后一行解决了这个问题-其实,人都无法取代&&&到所有通话getget_mut.我想这与&T实施有关Borrow<T>.

在这种解决方法中,我不确切地理解什么是说服编译器做我想做的事情.我不能直接将它应用于我的原始代码,因为我不使用引用作为键,而是嵌入引用的对象,所以我无法替换&&&...

att*_*ona 5

问题出现是因为不可变引用是其(引用)类型的变体,而可变引用对其类型是不变的.

理解这个概念的一个很好的读物是Nomicon.

HashMap:get对比get_mut

进一步缩小,这是一个简单的代码重现问题:

#![allow(unused_variables)]
use std::collections::HashMap;

fn main() {
    let mut hm = HashMap::<&u32, u32>::new();    // --+ 'a
    let c = 42;                                    // |  --+ 'b
                                                   // |    |
    HashMap::<&u32, u32>::get(&mut hm, &&c);       // |    |
    // HashMap::<&u32, u32>::get_mut(&mut hm, &&c);// |    |
}                                                  // +    +
Run Code Online (Sandbox Code Playgroud)

不可变的情况

考虑签名HashMap::get:

fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
    where K: Borrow<Q>, Q: Hash + Eq
Run Code Online (Sandbox Code Playgroud)

在这种情况下&Q&&'b u32get接收器是&Self.

不可变引用的变体性质意味着a &HashMap<&'a u32, u32>可以在&HashMap<'b u32, u32>需要的地方使用.

由于此规则,编译器会考虑原始调用:

HashMap::<&'a u32, u32>::get(&hm, &&'b c);
Run Code Online (Sandbox Code Playgroud)

相当于:

HashMap::<&'b u32, u32>::get(&hm, &&'b c);
Run Code Online (Sandbox Code Playgroud)

编译器从接口推断,并且仅从接口推断出方法实现不能引入泄漏:编译成功.

可变的情况

考虑签名HashMap::get_mut:

fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V>
    where K: Borrow<Q>, Q: Hash + Eq
Run Code Online (Sandbox Code Playgroud)

在这种情况下&Q&&'b u32,但get_mut的接收器&mut Self.

可变引用的不变性意味着a &mut HashMap<&'a u32, u32>不能在&mut HashMap<&'b u32, u32>预期的地方使用.

由于这个规则,编译器抛出一个错误,因为通过分析接口:

HashMap::<&'a 32, u32>::get_mut(&mut hm, &&'b c);
Run Code Online (Sandbox Code Playgroud)

例如,编译器不能排除get_mut可能存储生命周期的密钥'b.

这样的密钥不能比hmHashMap 更长:编译失败.