Option vs. Result advice

I have been returning Ok<Option<foo>> when something might not be there, such as a failed lookup, and Err<error> when there's been an error of some kind. However, I just looked at channel's try_recv() and see that it returns Err if there is nothing to read or if the channel is closed. Is that the pattern I should be using?

Getting Err is inconvenient in my case, because I want to supply a default value if the channel is empty and use ? to return if there's an error.

If Result<Option<Foo>, Error> best represents how you think about the behaviour, then you should absolutely use it.

2 Likes

It depends. For things like a key-value store, I'm a big advocate of returning Result<Option<T>, E> where Ok(Some(value)) means "here's the thing you asked for", Ok(None) means "the thing you asked for definitely doesn't exist", and Err(...) means "I can't say whether the thing you asked for exists or not because something went wrong".

I think APIs which require the callers to look at errors to make decisions about what to do next should be avoided; the common case for errors should just be "return to caller (maybe with some extra context)". The exception is the rare cases like where you have some retry logic of some form (but that introduces its own problems).

If failure isn't possible (eg, in memory local store), then just return Option<T>. Or if the semantics of the system are such that it is an error to ask for things that don't definitely exist, then use Result<T, E> (or map None to an error everywhere).

3 Likes

Returning Result<Option<T>, E> is perfectly fine. The thing you're seeing on channels is mostly something people do on channels and that's it.

3 Likes

Is there a reason it's done on channels? I tried to think of one but couldn't.

Either way is fine, and this is what the people who wrote it came up with.

I think it depends on your mental model. You have a try_lock on mutex and when you're using it, you're probably thinking that the mutex generally should be free, but one never knows and it's somehow possible to deal with the exceptional situation of not being.

Channels, as another synchronization primitive, might come into that category.

But of course in both cases you can have the mental model of just wanting to have a look if it might be accidentally free right now... in which case the API doesn't exactly match that model.

The nested options/results are often better and you even have methods that help working with them, like transpose.

I think Result<Option<T>, E> tells you the whole story by definition, which is why i like it more.
Result<T, E> does not tell me that one of the errors might be the fact that there is no T, unless i go read the definition of E.

It's worth noting that there are some channels that return Option<T> for recv, e.g. this one.