Last active
August 29, 2015 14:05
-
-
Save glaebhoerl/8795f00d90bec80bc400 to your computer and use it in GitHub Desktop.
Rustic exceptions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## Exceptions | |
Parts: | |
* union types / restricted Any | |
* match-on-type | |
* `throws Type`, `throw val` | |
* `try!` | |
Union types: | |
Either<A, B, C, D> | |
Can be any of A, B, C, or D | |
Like `Any`, but restricted to a set of listed types | |
Represented as (TypeId, UnsafeUnion<...>) (~ enums) | |
Either<Types..., T> = Either<Types...> if contains(Types, T) | |
T is *implicitly* coerced to Either<..., T, ...> | |
can test contained type like with `Any`, or use type-match | |
QUESTION what if Types contains trait objects? | |
avoid trait object auto-coercing in this case? | |
QUESTION what if Types contains a type variable? | |
does this work out OK? forbid it? | |
QUESTION what if Types contains another `Either`? | |
would naively lead to nested TypeIds | |
do that? flatten it? forbid it? | |
should be able to go from A to Either<A, B, C> *and* from Either<A, B> to Either<A, B, C>... | |
QUESTION is this related to OCaml's polymorphic variants? | |
(desired renames: Any -> Dynamic, Either -> Any) | |
special syntax: (A|B|C), A || B || C, A or B or C, ... ? | |
(A|B|C) with `foo is Type` for matching works out OK (no conflict with disjunction patterns) | |
Type match: | |
foo: (int|bool|char) | |
match foo { | |
i is int => ... | |
b is bool => ... | |
c is char => ... | |
} | |
or `i: int =>` (conflict: type ascription), or `i as int =>` (conflict: planned name binding), or `(type int, i) =>` (ok but ugly) | |
because set of types is fixed, can check exhaustiveness | |
(perhaps also allow this for "unrestricted" Any and require a wildcard?) | |
Throwing: | |
fn foo(Bar) -> Baz throws bool { ... } | |
`throw false` has type `!`, argument type checked against `throws` clause | |
or throw!(), like fail!()? eh, nicer if first-class | |
as with return type, so e.g. `fn foo() throws Box<Any> { throw box 666; }` works | |
algebraically isomorphic to returning `Result`, but exception is propagated by default (e.g. by unwinding) | |
functions without a `throws` clause, i.e. which don't throw, "throw" `!`, just like functions which don't return "return" `!` | |
basically this is making the Either monad a first-class part of the language, just like we've already done with ST and IO | |
this feels good! | |
contract violations are *not* part of `throws` clause and not catchable!! | |
assert/unwrap, array out-of-bounds, RefCell borrow check failure, ... | |
have to add `throws` to type of `fn` pointers, and additional typaram to `Fn` traits, e.g. `trait Fn<Args, Ret, Err>` | |
in surface syntax sugar, Ret defaults to `()` if `->` omitted, and Err to `!` if `throws` omitted | |
this seems reasonable and well-contained | |
Catching: | |
`fn try<T, E, Body: (|| -> T throws E) + Send>(body: Body) -> Result<T, E>` | |
task-like isolation, exception safety | |
`try! { ... }` => `try(|| { ... })` | |
also inverse: `fn throw_err<T, E, Body: || -> Result<T, E>>(body: Body) -> T throws E` | |
`try` and `throw_err` witness the isomorphism between `-> A throws B` and `-> Result<A, B>` | |
choose one or the other based on ergonomics | |
Put it all together: | |
fn some_op(arg: int) -> String throws (IoError|NetworkError) { ... } | |
fn other_op(arg: int) throws IoError { | |
let res = try! { some_op(arg) }; | |
match res { | |
Ok(s) => println!("ok: {}", s), | |
Err(ne is NetworkError) => println!("net err: {}", ne), | |
Err(ie is IoError) => throw ie | |
} | |
// some_op(arg); error: can't throw NetworkError | |
// throw 9i; error: can't throw int | |
} | |
have to list throwable type(s) explicitly, type error if you try to throw something not listed, but propagation is implicit | |
could possibly work on syntax more (`match try! { ... } { ... }` looks weird), but this is quite tolerable | |
(unhandled errors are rethrown explicitly... not sure if this is a problem) | |
various parts are orthogonal: | |
can do `throws`, `throw`, and `try` without union types and type-match to get just the automatic propagation | |
can do just `(A|B|...)` / Either<A, B, ...> and type-match and use it with `-> Result<Foo, (A|B|C)>` for just the automatic coercion | |
but probably need both to make it really seamless | |
e.g. if have just throwing, need to use enums in return type and catch-and-rethrow to embed, or trait objects (`Box<Any>`) which allocate | |
if just union types, need to return `Ok()` and use (current incarnation of) `try!` everywhere |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment