To respond however to the mental model point. As I said, @tomaka wrote:
Returning None is part of the normal workflow of a successful program, while returning Err always indicates an error.
I am sympathetic to this argument. I think a lot of people have trouble getting a “feeling” for when it is appropriate to use Result and when to use Option. And of course while we’re discussing “error propagation”, we have to bring in panic as well.
That said, if there is one thing I have learned about error handling over the years, it’s that the distinction between an “error” and “normal control flow” is often very context dependent. I think there are plenty of cases where one could reasonably use Option or Result and either would be reasonably suitable. Moreover, if we limit ? to Result, I think what we will see is that a lot of people will use Result just so that they can take advantage of the ? syntax, which may or may not be what we want to encourage.
@nrc gave an interesting example in the lang team meeting. He pointed out rustfmt’s basic architecture is that you can invoke a format() method on a given formatting rule or strategy, which attempts to apply it to a piece of Rust code – it either returns Some, in which case the rule applies, or None. (@nrc, correct me if I am summarizing poorly here of course, I’ve never actually written anything in rustfmt myself.) If the result is None, the typical thing is to go off and try another strategy. Or something like that. Anyway, the key point is that it would be very handy to use ? sugar here for the recursive portions of the computation (and I think that rustfmt in fact uses a variant on try! for this). On the one hand, one could view “failure to apply” as an error, but it’s not “unexpected” in any sense of the word.
I also know that in practice I have frequently wanted to use ? (as well as catch, which we have not yet implemented) with Option values, though I can’t recall precise scenarios. I think just when composing complex predicates where I would otherwise use a lot of early return.
Another interesting question for me, which I think is relevant to this objection, is whether to allow mixing Option and Result (and possibly other Carrier implementers). If you consider them to be “categorically different” than it seems risky to permit an Option<T> to be quietly converted to Result<T,()> (or vice versa). This is a bit of mental dissonance for me personally, since I simultaneously see overlap between the two but also think I would prefer to avoid silent conversions between them – perhaps because I see only partial overlap (i.e., there are plenty of use cases where only one is applicable).
Anyway, I’m still turning this over in my mind.