Which is more idiomatic: Option<Result<T>> or Result<Option<T>>?

Result<Option<T>> helps propagate errors more easily, but for some reason, Option<Result<T>> seems to be the one more widely used in APIs (edit: probably because it's closer in form to the iterator API). Does this matter at all, or am I overthinking it?

2 Likes

Not sure either is more idiomatic than the other. They convey different things.

1 Like

Sorry, I thought that they were effectively the same (each expands to a sum type with error, value, and "done" variants). What does each convey?

In my particular case I'm trying to make an iterator, but because the value of Iterator::next can't borrow self, I can't use the trait.

Result<Option<T> says you had a fallible operation. It might produce None, which has some meaning. This is used in, eg, future’s Stream to signal end of stream. While trying to get the next element from the stream, it might’ve failed - that’s the Err.

Option<Result<T> says you may not have a value at all - like exhausted iterator.

So the semantics/usage are slightly different. The former says “I did something that might’ve errored and here’s a possibly empty result” and the latter is “I may already have something but it may have errored”.

6 Likes

Sorry, I don't understand the difference between end-of-stream and end-of-iterator in your examples.

Getting the next element from iterator is infallible. It may be iterating over fallible results but the iterator itself cannot fail in determining whether it has this result or not.

Getting the next element from the stream is itself fallible but the result it has isn’t. Think of a tcp connection you’re reading data from. When you go to get the next element (bytes) you realize the connection has been ungracefully terminated that’s an outright Err. Or you get a read of 0 and you know the stream is done and there’s no more data.

1 Like

I'm having a bit of a deja vu :slight_smile: This might be interesting related reading:

The difference is in the nesting, and thus also which part of the operation the Err applies to.
I tried to write some more examples, but they all end up being basically what Vitaly already wrote...

The difference is indeed quite subtle, and I think there are some cases where Result<Option<>> is better, and other cases where Option<Result<>> is better.

1 Like

For visitors like me, I think the definitions of Result and Option helps to see how affects the order.

enum Result < T, E >{
    Ok (T),
    Err(E),
}

enum Option < T > {
    None   ,
    Some(T),
}

So it seems being about where would be more useful have the None as a expected return.

By the way. I do not know what performance penalty does have joining the above combinations, and if something like the following would performs better, or not:

enum Panout <T, E>{
    None   ,
    Some(T),
    Err (E),
}

In most cases options will be null pointer optimized, meaning that no extra tag is needed to differentiate between a None or a Some. Which means an Option<Result<T, R>> (or vice versa) would use the same amount of space as just a Result<T,R>.

1 Like