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.