Redirectk
Redirectk

Reputation: 222

Returning a &HashMap from rust function

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:

  1. Add a private field in struct S that is just an empty HashMap so that I can return a reference of. This seems a bad lazy solution.
  2. Call entry(a).insert_with(|| HashMap::new()), but this requires unnecessary mutability.
  3. Return Option<&HashMap>

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

Answers (3)

sudormrfbin
sudormrfbin

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

Chayim Friedman
Chayim Friedman

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

drewtato
drewtato

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

Related Questions