Skip to content

Instantly share code, notes, and snippets.

@glaebhoerl
Last active August 29, 2015 14:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save glaebhoerl/8795f00d90bec80bc400 to your computer and use it in GitHub Desktop.
Save glaebhoerl/8795f00d90bec80bc400 to your computer and use it in GitHub Desktop.
Rustic exceptions
## 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