Reputation: 222
I am struggling to find an elegant to solution to access a nested map for read-only purposes in rust. I have a situation of this kind and ideally as example I could return a reference to an empty map (that of course doesn't work since the empty hashmap is owned by the function):
struct S {
stuff: HashMap<A, HashMap<B, C>>
}
impl S {
fn get(&self, a: &A) -> &HashMap<B,C> {
return self.stuff.get(a).unwrap_or(&HashMap::new());
}
}
There is no guarantee that the map stuff will have the key a, hence handling optionality is a must.
I would like the solution to implement the method get with this or similar signature in an efficient way (no copies/moving) because the map stuff can be quite big.
I could only think of the following solutions, but I am thinking there must be a more straightforward one:
It seems to me that the solution (3) is the best way to achieve the above, but maybe I am missing something and there is a more straightforward way?
Upvotes: 2
Views: 346
Reputation: 746
An alternate solution is to use a Cow
(as mentioned by John Kugelman in the comments):
fn get(&self, a: &A) -> Cow<HashMap<B, C>> {
self.stuff
.get(a)
.map(Cow::Borrowed)
.unwrap_or(Cow::Owned(HashMap::new()))
}
// or even better:
fn get(&self, a: &A) -> Cow<HashMap<B, C>> {
self.stuff.get(a).map(Cow::Borrowed).unwrap_or_default()
}
An advantage over using Option
is that you can seamlessly support returning an owned HashMap
if you need so in the future, without breaking API compatibility. Cow
also implements Deref
, so you can directly call methods on the HashMap
which is a win for ergonomics:
if s.get(&a).contains_key(&b) {
println!("key exists");
}
Upvotes: 1
Reputation: 71430
Since the reference is immutable, you can provide a reference to a "global" HashMap
.
fn get(&self, a: &A) -> &HashMap<B, C> {
static GLOBAL_MAP: std::sync::OnceLock<HashMap<B, C>> = std::sync::OnceLock::new();
return self
.stuff
.get(a)
.unwrap_or(GLOBAL_MAP.get_or_init(HashMap::default));
}
This may be faster than using Option
if you need to perform a lot of operations on the map.
Upvotes: 1
Reputation: 12812
Returning Option<&HashMap>
is idiomatic and simple.
fn get(&self, a: &A) -> Option<&HashMap<B,C>> {
self.stuff.get(a)
}
Anything you might do with &HashMap
can be done with Option<&HashMap>
. For instance, you can check if a key is present.
if s.get(&a).map_or(false, |hm| hm.contains_key(&b)) {
println!("key exists");
}
This produces false
when get
returns None
, which is the same as if you call contains_key
on an empty HashMap
.
As a bonus, an Option
containing a reference is always the same size in memory as just the reference.
Upvotes: 5