In Rust, using a HashMap
where the key is a &str
that refers to a String
member within its corresponding value is not directly possible. The following example illustrates the idea:
type PersonMap = HashMap<&str, Person>;
struct Person {
name: String,
}
This is not feasible due to Rust's ownership and borrowing rules and HashMap
's resizing, as explained in this answer (https://stackoverflow.com/a/61021022/84540).
However, I wonder if there exists an unordered map implementation that supports pointer stability (What is pointer stability?), which could make such usage possible. Or is it not possible due to Rust's lifetime checking? Thanks.
You do not need pointer stability to support such a mapping, even without cloning keys. The trick is to use HashSet
instead with a wrapper that makes its value behave like its key.
Here's how that could look:
use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
use std::collections::HashSet;
#[derive(Debug)]
struct Person {
name: String,
// other fields
}
#[derive(Debug)]
struct PersonByName(Person);
impl PartialEq for PersonByName {
fn eq(&self, other: &Self) -> bool {
self.0.name == other.0.name
}
}
impl Eq for PersonByName {}
impl Hash for PersonByName {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.name.hash(state);
}
}
impl Borrow<str> for PersonByName {
fn borrow(&self) -> &str {
&self.0.name
}
}
fn main() {
let people = vec![
Person { name: "Jimmy".to_owned() },
Person { name: "Timmy".to_owned() },
Person { name: "Lenny".to_owned() },
];
let map: HashSet<PersonByName> = people.into_iter().map(PersonByName).collect();
println!("{:?}", map.get("Timmy"));
}
Some(PersonByName(Person { name: "Timmy" }))
So even though its a "set" and not a "map", we can treat it like a map by making use Rust's Borrow
trait when .get
-ing a value. The Hash
and PartialEq
/Eq
are there to make it behave like just a string so the HashSet
lookup is consistent.