HashMap entry with Result

While I can do what I want with an explicit match statement, I’d like to figure out how to make the following code work. The problem is that one of the steps in the closure returns a Result. (I’m using Failure if that makes a difference.)

let foo = some_hash_map.entry(&bar)
      .or_else_with(|| -> Result<Foo, Error> { 
           do_some_stuff();
           some_calculation_that_returns_a_result_enum()
       });

Not surprisingly, the compiler tells me I’ve got Result when I want a Foo. Of course,

let foo = some_hash_map.entry(&bar)
      .or_else_with(|| { 
           do_some_stuff();
           some_calculation_that_returns_a_result_enum().unwrap()
       });

works, but I don’t want to panic. I also prefer not to eat the error in the closure.

I could write

let default = some_calculation_that_returns_a_result_enum()?;
let foo = some_hash_map.entry(&bar)
      .or_else_with(|| { 
           do_some_stuff();
           default
       });

That’s not a problem if calculating the default value is cheap, but what if it isn’t?

I assume you mean or_insert_with in your examples?

I don’t think there’s an API to help here, and a match is currently your best bet.

If we were to add something to Entry, I imagine it would be based on the Try trait. I’m not sure how to write that though, because we’d need the FnOnce closure to return a Try type with the V value, but then somehow transform that to a similar Try type with &mut V to return. Maybe that just has to return Result instead.

" I assume you mean or_insert_with in your examples?"

Yes. That’s what I get for trying to simplify my example instead of using my actual code.

The other thing that occurred to me was to store Ok(value) in the HashMap, but I think I’ll just stick to the match statement for now.

I’m interested in this pattern because I’m trying to get away from

match option_of_something {
      Some(foo) => foo,
      None => something_else
}

and the same for Result. I find the various or_xxx patterns easier to read.

Hmm, good point that this really wants to be able to accept a type constructor, not a concrete type. Best I can come up with right now is something like

pub fn or_insert_with<F: FnOnce() -> V, T: Try<Ok=V>, R: Try<Ok=&mut V>>(self, default: F) -> &'a mut V where R::Error: From<T::Error>

But I doubt that’s the signature anyone actually wants.

I think it isn’t too bad to just return a Result:

pub fn try_or_insert_with<F: FnOnce() -> T, T: Try<Ok = V>>(self, f: F) -> Result<&mut V, T::Error>;

I might be misinterpreting what you mean by “eat the error” here, but this strikes me as (part of) the source of your conflict. Fundamentally, there are three things you can do in the closure with errors:

  1. unwrap and potentially panic
  2. deal with them and hide (eat) them so you return a bare Foo, perhaps in some way that implictly encodes a masked error, like a default value
  3. pass them through and deal with them later (return the Result and store that in the hashmap).

Regardless of the syntax of how you extract the Foo, it seems to me you’re perhaps still undecided about your strategy and requirement here.

Footnote: I’ve found myselt sometimes doing a mapping from Result to Option, but that’s just a variant of 2 that still leaves you to do the work of 3. In those cases it makes sense because they’re truly optional values (like config items from a config file that might not exist, etc).

I agree it’s not terrible, but I’d rather not give people NoneErrors in Results if I can help it, so I’d probably rather just force both to be Result until we get enough GAT support to make the API we obviously want. We can always name it or_insert_with_result or something for now, if there’s concern it couldn’t be compatibly expanded to be generic later.