let _: Result<Foo, Bar> = catch {
foo()?; // try or bubble?
fail Bar(0); // break w/ err-wrapping, replaces throw
fail Baz(0); // like `goto handle` w/ handle identified via pattern match.
pass Foo(0); // break w/ ok-wrapping
Foo(0) // implicit ok-wrapping
}
handle Baz(0) { // must break with either Ok(Foo) or Err(Bar)
fail Biz(1); // break with Err-wrapping
pass Foo(1); // break with Ok-wrapping
Ok(Foo(2)) // return explicit value (no ok-wrapping)
}
And the eval-based example:
let _: Result<Foo, Bar> = eval { // like "evaluation"
foo()?; // try operator
fail Bar(0); // break w/ err-wrapping, replaces throw
fail Baz(0); // like `goto handle` w/ handle identified via pattern match.
pass Foo(0); // break w/ ok-wrapping
Foo(0) // implicit Ok-wrapping
}
handle Baz(0) { // must break with either Ok(Foo) or Err(Bar)
fail Biz(1); // break with Err-wrapping
pass Foo(1); // break with Ok-wrapping
Ok(Foo(2)) // return explicit value (no ok-wrapping)
}
I believe the handle semantics could be useful in solving the problem RFC 243 points out about match sugar:
There was some concern about potential user confusion about two aspects:
- catch { } yields a Result<T,E> but catch { } match { } yields just T;
- catch { } match { } handles all kinds of errors, unlike try/catch in other languages which let you pick and choose.
The handle logic presented above runs on fail, and can pattern match on the type of any fail, but they must return a value consistent with the statement's type (in the above examples, handlers must return Result<Foo, Bar>). The fail and pass shortcuts are still available, but fail handlers don't recurse, so the type returned in fail must have a std::convert::From implementation to the expression's failure type.