This is an idea that’s been in my head for a while, and I’d like to get some feedback on it. This is something that I started thinking about again given Niko’s recent post about the return value of main
. I really only dabble in Rust, so there might be valid reasons to shoot this idea down immediately, but I thought I’d post it here for critique anyway.
Summary
Make libstd
reexport Result
with the error case type parameter defaulted to Box<Error>
or Box<Error+Send>
.
Motivation
In my opinion it’s a huge stumbling block what the error type in a Result
is supposed to be. As a newcomer to Rust, I found this very frustrating, and that was at a time when the situation was far simpler than it is today. Imagine all the things one needs to learn to define a proper error type today, it’s quite complicated. Yet there is an error type which solves this problem, especially for small applications which might not need very sophisticated error handling: Box<Error>
.
The problem with this error type is that we barely advertise to newcomers that it exists. There’s a section about it at the end of the book, but I think that’s a disservice, this should be the first way that Rust newcomers learn to handle errors, and they can learn about the more sophisticated types of error handling later.
One barrier in using Box<Error>
is the actual type name. It requires one to understand trait objects, virtual dispatch, the limits thereof (e.g. can’t downcast to a trait), and so on.
Detailed design
Since Box<T>
is defined in liballoc
, we can’t simply change the definition of Result
in libcore
. Instead libstd
will reexport a type alias like this instead:
pub type Result<T, E=Box<::std::error::Error+Send> = ::core::result::Result;
Currently this is not backwards compatibility, since you can’t do Result::Ok
through a type alias, but there’s an open issue for this which has been determined to be a bug and that being able to construct a variant through a type alias should be possible. If this issue is fixed then this change should, in theory, be backward-compatible.
This would make it possible to write functions like this:
fn something() -> Result<Baz> {
foo()?.bar()?.baz()
}
I think this is very attractive, and would make a hypothetical change to main
's signature much more attractive:
fn main() -> Result<()> {
foo()?.bar()?.baz();
Ok(());
}
How We Teach This
The whole advantage to this is that we do not teach this. We can simply present the Result
type without even mentioning what the default error type even is, and explain this in more detail in the error handling chapter. The attractive part about this is that we encourage new users into (reasonably) good practices by avoiding unwrapping, without having to go into all the details of error handling, defining error types, trait objects, the From
trait and all of that.
Result
plus ?
plus unwrap
/expect
together form a pretty solid, and much simpler error handling story for beginners.
Drawbacks
I am unsure if this is really backwards compatible (the issue above aside), I’d like to hear opinions on this.
If a more comprehensive solution to error handling is adopted, such as checked exception via throws
as suggested at some point, then this solution might become obsolete.
It is yet another thing to learn, though as explained above, I hope that this improved the learning experience, rather than adding additional burdens.
It adds a difference between libcore
and libstd
, but I think this is not a huge problem, since those working with libcore
will usually be more advanced Rust developers anyway, for whom this won’t be a huge addition to cognitive load.
EDIT: Another drawback is that is somewhat blurs the line between Result
and Option
.
Alternatives
Wait for a more comprehensive solution, such as checked exceptions.
Do nothing.
Be even more radical and default the first type parameter to ()
, this makes the signature of main
even nicer, but I find this confusing, since there’s no way to construct a valid return value for such a function without writing out an empty tuple anyway, so it seems more confusing than helpful.
Unresolved questions
Should there be a Send
bound on the error?