Returning an iterator depending on a HashMap lookup

Hello!

I was trying to return an empty iterator if my HashMap key doesn't exist:

    pub fn find(&self, search: &str) -> impl Iterator<Item = PopulatedNode> {
        self.lookup
            .get(&NodeLookup::Id(search.to_string()))
            .unwrap_or_else(|| &vec![])
            .into_iter()
            .map(|n| self.populated(*n))
    }

I realize the iterator needs to keep the Vec around so I can't just give it a Vec that will be out of scope after this function returns. I tried multiple things but ended up with a dirty hack:

static EMPTY_USIZE_VEC: Vec<usize> = vec![];
...
    pub fn find(&self, search: &str) -> impl Iterator<Item = PopulatedNode> {
        self.lookup
            .get(&NodeLookup::Id(search.to_string()))
            .unwrap_or(&EMPTY_USIZE_VEC) // <--
            .into_iter()
            .map(|n| self.populated(*n))
    }

One thing I tried was to create my own iter::Map, but couldn't work out how to instantiate it myself (private).

I was considering trying out .entry() but I don't think having to change &self to be mutable is an ideal solution.

Is there a better way?

What you’ve got isn’t terrible; the main issue you’re running into is that every codepath has to return the same concrete type. There are a few additional choices, but I’m not convinced any of them is better than what you already have:

  • Return Option<impl Iterator<...>>
  • Use dynamic dispatch, and return a Box<dyn Iterator<...>>
  • Make an enum that has variants for both std::iter::Empty and the iter::Map, and implement Iterator for it.

If it were me, I’d probably move the static inside the function body and call it a day.

1 Like

This is quite possible:

self.lookup.get(…)
    .map(|vec| vec.as_slice())
    .unwrap_or(&[]).
    .iter()

That gives you an impl Iterator<Item=&PopulatedNode>
Add a .cloned() if the reference is a problem.

3 Likes

Works perfectly, thanks!

How does &[] work where &Vec::new() doesn't? What owns []?

&[] is an empty slice zero length array, so a reference to some 0 elements of data. That means it does not need a valid pointer and it can be created at any time with any lifetime.

&Vec::new() is a reference to an empty Vec. But that Vec needs to live somewhere. You could use a static empty Vec for it, but a slice is cleaner.

5 Likes

Is it an empty slice, or an empty const array? Either would work here, as const values are stored in the executable, and have &’static references.

From the compiler's point of view, it's a reference to a constexpr array, which gets promoted to static and then coerced to a slice reference.

6 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.