我有一个参考struct Foo<'a>
包装&'a str
.我想HashMap
用Foo
s作为键来填充.这是一段代码(在操场上打开):
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
),这是完全可以理解的:我不能移动c2
到HashMap
,这生活比借用数据不再c2
从c
.
但是,我不明白为什么倒数第二行(get_mut
)有同样的问题:在这种情况下,借用的数据应该只在调用期间是必需的get_mut
,它不会被移入HashMap
.
这是更加令人惊讶的是,get
上述工程完美(如我所料),以及两个get
与get_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
在最后一行解决了这个问题-其实,人都无法取代&&
的&
到所有通话get
和get_mut
.我想这与&T
实施有关Borrow<T>
.
在这种解决方法中,我不确切地理解什么是说服编译器做我想做的事情.我不能直接将它应用于我的原始代码,因为我不使用引用作为键,而是嵌入引用的对象,所以我无法替换&&
为&
...
问题出现是因为不可变引用是其(引用)类型的变体,而可变引用对其类型是不变的.
理解这个概念的一个很好的读物是Nomicon.
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 u32
和get
接收器是&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
.
这样的密钥不能比hm
HashMap 更长:编译失败.