Shortcut for hashmap get returning result?

I'm working with a lot of hashmaps currently and using get frequently enough to where it saved quite a bit of typing creating a helper for converting it into returning a Result instead of an Option like so:

fn get<'a, K, V>(key: &'a K, map: &'a HashMap<K, V>) -> Result<&'a V, Error>
where
    K: Eq,
    K: PartialEq,
    K: Hash,
    K: Display,
{
    map.get(key)
        .ok_or_else(|| Error::from(format!("missing key: {key}")))
}

Where Error is my own custom type. This is useful in avoiding formatting the error with tons of occurrences in my code.

It dawned on me this is quite a bit of plumbing to do something pretty fundamental. Is there already a wheel I've reinvented here for converting the outcome of get to a result like is being done above?

There's no shortcut in this case.

Under most circumstances, I would probably advise against putting that in a separate function (my personal taste is that having the expression on its own is more explicit) but you can do it however you like.

Out of curiosity, can you show us a little more about your error type (and why you need to be generic over keys and values)? Maybe we can find a better way with a little more context about your situation.

1 Like

There's multiple hashmaps, each with their own key and value types, so I reuse it for each. I hear you about explicitness, but after a dozen or so get calls it gets to be quite an eyesore too.

The Error is just a good ole fashion pub type Error = Box<dyn std::error::Error>;

I guess I was hoping there'd be someway to implement From for the Option type returned by get so that it was more neatly colocated.

I'm not saying that it's wrong or useless, but personally I've never needed that. I don't see much benefit of wrapping the optional value in some opaque result. You can't do much with it that you can't do with an Option.

missing key: …“ seems like a remarkably unspecific error. How is this error being used?

Is is supposed to be an error displayed to the user? Then it could be nicer, e.g. more specific to the erroneous user interaction that this error represents. For a more helpful error message, of course you’ll need to “re-invent the wheel” as the error message is then going to be specifically adapted to your application; and maybe you even want different errors for different hash maps.

Or is it an error that should never actually happen, unless there’s a bug in your program? Then, why not unwrap()?


If the key weren’t included, you could simplify the helper function by merely converting the Option<T> into the appropriate Result. If you want to write even more code for the helper, the call site can become even “nicer”, in method-call style, by using an extension trait. (Also, for more generality, you could mirror the generic K: Borrow<Q>-style API of the actual HashMap::get.)

1 Like

opaque - yes, but allows me to do get(k, map)? and because it's no longer an option ? works consistently with my other Result types

I would do unwrap but I thought then zero context would be shown to the user? I want the small amount specified in the error as it's just enough to trace down where something is missing.

Yes it's quite ugly, but it's for developers so I'm okay with brief errors.

For what it's worth, more proper bounds would be

fn get<'a, Q, K, V>(key: &Q, map: &'a HashMap<K, V>) -> Result<&'a V, Error>
where
    K: Borrow<Q> + Hash + Eq,
    Q: Display + Hash + Eq + ?Sized,

(but at least don't force the borrow of the key and map to be the same; that could limit you if your owned key is local and you want to return something from a borrowed HashMap, say).

2 Likes

If it's for other developers, you might want to try anyhow with it's feature backtrace. It also has a convenience function to convert an option to an result with a context string. Then you can use the question mark operator again.

map.get("key").context("no key found")?;
1 Like

I knew there'd be a wheel already! Wow, that's some lovely API right there...

Actually I can't believe I missed this! the standard library has expect("additional context")

While it isn't a result type, it does fail with satisfactory information.

Another way to achieve this would be

||->Option<T> {
     a.get(b)?
     c.get(d)?
     e.get(f)?
     ...
     Some(x)
}().unwrap_or(..)
1 Like