In my heart, the best idea of error handling is throw a exception
rather than return a ADT such as result
, but neither checked exception
or dynamic checking exception
, is the compile-time auto deduction exception
. I don't know is there a similar theory about it, but I think it is cool.
how it works?
Let us look a piece of code:
arr.map(|item| do_something(item));
This is a general functional operation of array.
But in real world, error will happen any time, although during iterating, so do_something
may not success, maybe result a error.
But, it's sad that the map
function only accept a closure which return a ()
, we can't return a Result
!
So, how can we do?
-
Turn
map
tofor
, we can return aResult
solved this problem, but it's not functional. In some case, it isn't a iterator, we has no idea. -
Use
unwrap
,expect
orpanic
, and then we usecatch_unwind
to handle the panic, it's work well, but not suit the rust's philosophy.
And let we jump out of rust
, try checked exception
and dynamic checking exception
?
- checked exception: not works, because it also needs function meta declaration.
-
dynamic checking exception
: it works, just likepanic
, but it's runtime exception handling, we can't know the real type of exception during compile-time, must downcast the type of exception to detect what exception we met, so not suit the rust's philosophy.
Now we explain about the compile-time auto deduction exception
, I will wite some pseudo-code.
The defined of do_something
:
fn do_something() {
throw MyException::new("A little error");
}
Look just like dynamic checking exception
, but when compile, it will expand to:
enum __AnonymousExceptionBucket_A__ {
MyException { message: String },
}
fn do_something() -> Result<(), __AnonymousExceptionBucket_A__> {
return Err(MyException::new("A little error"));
}
So throw
is just a syntax sugar of Result
.
And when calling the map
:
arr.map(|item| do_something(item));
It will expand to:
arr.map(|item| -> Result<(), __AnonymousExceptionBucket_A__> do_something(item));
Not just the function meta declaration of do_something
will change, but the caller map
will change too.
What about multi Exception type throws in a function? I think dealing Result
type cast is so boring in rust, let we see how compile-time auto deduction exception
works.
fn do_something() {
do_something_first();
throw MyException::new("A little error");
}
fn do_something_first() {
throw MyAnotherException::new("Another little error");
}
Expand to:
enum __AnonymousExceptionBucket_A__ {
MyException { message: String },
MyAnotherException { message: String },
}
fn do_something() -> Result<(), __AnonymousExceptionBucket_A__> {
do_something_first()?;
return Err(MyException::new("A little error"));
}
enum __AnonymousExceptionBucket_B__ {
MyAnotherException { message: String },
}
fn do_something_first() -> Result<(), __AnonymousExceptionBucket_B__> {
return Err(MyAnotherException::new("Another little error"));
}
// Omit some auto generated trait implementation...
How to handle these exception? it's also easy:
try { // try will return a `Result`, because it's a sugar.
arr.map(|item| do_something(item));
}.catch { // it's the syntax same as `.await`, behave like `.match`.
MyException(message, ...) => { ... },
MyAnotherException(message, ...) => { ... },
// Not need to exhaust the enum.
}
But if we want to catch a Exception
not appeared, we will receive a compile error:
try {
arr.map(|item| do_something(item));
}.catch {
MyException { message, ... } => { ... },
MyAnotherException { message, ...} => { ... },
NotKnownException { message, } => { ... }, // Not compiled! because is not an element of enum.
}