Thank you for this summary, it really helps organize the many things going on here!
To me, a major strength of Rust is a very simple, core feature: I can use types as footholds in reasoning about my programs. Matching on enums (reasoning by cases!) is my favorite example of this.
I found myself struck by this line:
I think it could be very useful to think of error handling as âexceptions that one must manually propagate at each levelâ and then, once youâve learned about enums etc, understand how that works under the hood.
This seems out-of-order: I would teach a new Rust user about enums immediately, long before the (important!) practical matter of using Result idiomatically.
Here is a data point from another language family: in the past, before I had a need to use them, I found myself a bit confused by async function and function* in JavaScript. The first time I went to use those features, it was while writing TypeScript. It was there, seeing the âexternalâ return type written out, that made everything really click for me.
Now, conflating a T and a Promise<T>, or a T and a Result<T, E> can be either helpful or harmful. In the JS case, after seeing the concrete, true type of an async function, I found it easy to go back elsewhere (e.g. in a plain JavaScript codebase) and conflate the types in a way that was helpful: I always felt like I was on solid ground, because I had the true type in my head.
Similarly, when I have a Rust function that returns a Result<T, E>, I really do mostly think of its return type as a Result<T, E>, not a Tâ even when I locally want to conflate the two. Because of this, I have found it extremely easy to write my own error handling helpers, and think about how they might be composed.
The ? operator is really great, in that framing, because it lets me âunwrap or bailâ explicitly. Note that I think of it as âbailâ, as in early return, and not âthrow an exception, kindaâ. I know exactly where Iâm bailing to: historically, the end of the function, or in the case of a catch block, another explicit, function-local place.
When I write error-handling in Rust, I am not thinking of ? as marking a âhappy pathâ (like I might with, say, a hypothetical do notation): I am always thinking of as a shorthand for a particular (tedious, verbose) type of match-and-conditional-branch. In that sense, catch blocks are nice without the exception analogy, because I can think of it as âbreak from this blockâ (as if I were in a loop), vs. âreturn from this functionâ.
I happily use TypeScriptâs try/catch for promises, and I like the idea of throw. But, unlike many languages, Rust has a sturdy foundation of real enums and pattern matching. I think that should be the framing that we emphasize and prioritize when teaching and improving error handling, whatever the syntax.