What is the proper way to index into a HashMap<&String, V>
with a &str
? Rust reports that &String
is not Borrow<str>
, which is required for indexing. (This seems a bit silly to me; if T: Borrow<U>
then surely &T: Borrow<U>
should also hold?)
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>`
Obviously, because I constructed this particular HashMap, I could go back and change its key type to &str
instead. But supposing I'm handed a HashMap<&String, V>
, what is the proper way to index it? Constructing a whole String
just to take a reference to it seems wasteful.
There is a way around this if you are willing to dip into unsafe waters.
This solution takes advantage of the fact that if you have a struct S
marked #[repr(transparent)]
and containing a single field of type T
, you can safely transmute from &'a T
to &'b S
where 'a: 'b
. (This is why you can borrow a &Path
from a PathBuf
even though no Path
ever actually exists, for example.)
So, let's build a newtype that we can borrow from String
, and with a positively bikesheddable name.
#[derive(Hash, PartialEq, Eq)]
#[repr(transparent)]
struct StrRef(str);
This definition allows us to safely transmute from &str
to &StrRef
, so we'll wrap that bit of unsafe code up in a From
implementation:
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) }
}
}
Now we can implement Borrow<StrRef>
on any type from which we can get a &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()
}
}
Finally, this allows us to write the following function, which accomplishes your goal of fetching from the map using a &str
without converting it to an owned String
:
fn get_from_map<'a, T>(map: &'a HashMap<&String, T>, key: &str) -> Option<&'a T> {
map.get(<&StrRef>::from(key))
}
Obviously, if you can change the map's key type to &str
then you should prefer that, but if you don't have control over where the map comes from then this is a viable workaround (in a -- hopefully -- very limited scope within your program).