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
maptofor, we can return aResultsolved this problem, but it's not functional. In some case, it isn't a iterator, we has no idea. -
Use
unwrap,expectorpanic, and then we usecatch_unwindto 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.
}