用
HashMap<&String, V>
索引 &str
的正确方法是什么? Rust 报告称 &String
不是索引所需的 Borrow<str>
。 (这对我来说似乎有点愚蠢;如果 T: Borrow<U>
那么 &T: Borrow<U>
肯定也应该成立?)
use std::collections::HashMap;
fn main() {
let strings = vec!["x".to_owned(), "y".to_owned(), "z".to_owned()];
let m = [0, 1, 2]
.into_iter()
.map(|i| (&strings[i], i))
.collect::<HashMap<&String, usize>>();
let zero = m["x"];
}
error[E0277]: the trait bound `&String: Borrow<str>` is not satisfied
--> src/main.rs:9:18
|
9 | let zero = m["x"];
| ^^^ the trait `Borrow<str>` is not implemented for `&String`, which is required by `HashMap<&String, usize>: Index<&_>`
|
= help: the trait `Borrow<str>` is implemented for `String`
= help: for that trait implementation, expected `String`, found `&String`
= note: required for `HashMap<&String, usize>` to implement `Index<&str>`
显然,因为我构建了这个特定的 HashMap,所以我可以返回并将其键类型更改为
&str
。但是假设我收到一个 HashMap<&String, V>
,索引它的正确方法是什么?构建一个完整的String
只是为了引用它似乎很浪费。
如果您愿意浸入不安全的水域,有办法解决这个问题。
此解决方案利用了以下事实:如果您有一个标记为
S
的结构体 #[repr(transparent)]
并包含单个 T
类型的字段,则可以安全地从 &'a T
转换为 &'b S
(其中 'a: 'b
)。 (例如,这就是为什么您可以从 &Path
借用 PathBuf
,即使实际上不存在 Path
。)
所以,让我们构建一个可以借鉴String
的新类型,并具有一个积极的自行车棚名称。
#[derive(Hash, PartialEq, Eq)]
#[repr(transparent)]
struct StrRef(str);
这个定义允许我们安全地从
&str
转换为
&StrRef
,因此我们将把那段不安全的代码包装在 From
实现中:impl<'a> From<&'a str> for &'a StrRef {
fn from(value: &'a str) -> &'a StrRef {
// SAFETY: StrRef is repr(transparent) and contains only a str.
unsafe { &*(value as *const str as *const StrRef) }
}
}
现在我们可以在任何可以获得
Borrow<StrRef>
的类型上实现
&str
。// Not actually needed, just for example.
impl Borrow<StrRef> for String {
fn borrow(&self) -> &StrRef {
self.as_str().into()
}
}
// This is the impl you need in this case.
impl Borrow<StrRef> for &String {
fn borrow(&self) -> &StrRef {
self.as_str().into()
}
}
最后,这允许我们编写以下函数,它实现了使用
&str
从地图中获取数据的目标,而无需将其转换为拥有的
String
:fn get_from_map<'a, T>(map: &'a HashMap<&String, T>, key: &str) -> Option<&'a T> {
map.get(<&StrRef>::from(key))
}
(游乐场
&str
,那么您应该更喜欢这样做,但是如果您无法控制地图的来源,那么这是一个可行的解决方法(希望是非常可行的)您的计划范围有限)。
与现有
冲突
impl<T> Borrow<T> for T {
所以无法添加。游乐场
&String
作为开始键。首选方法是使用
&str
,它可以轻松地从 &String
创建,并且不会遇到问题,而且更通用。当您无法控制时,创建 String
只是为了索引是您唯一安全的选择。