3 ms·
The hard part of checked exceptions is when they start interacting with higher order code- in Java this often happens when an interface method wants to throw. E
by Rusky 3y ago
The hard part of checked exceptions is when they start interacting with higher order code- in Java this often happens when an interface method wants to throw. Each implementation may want to throw its own set of exceptions, but the code that just works with the interface doesn't care about any of them, and existing languages don't really give you a way to express this.
What you really want here is polymorphism in the exception specification, and a way for consumers to allow these new exceptions types to "tunnel" through out to the caller that knows about them. And for that to work while still providing any guarantees that exceptions are handled, it turns out you need something like a borrow checker, to prevent the interface object from escaping that outer scope!
Here's a 2016 paper on the subject (with Barbara Liskov as a coauthor): https://www.cs.cornell.edu/~yizhou/papers/exceptions-pldi2016.pdf https://www.cs.cornell.edu/~yizhou/papers/exceptions-pldi201....
- pjmlp 3y agoJust like mixing errors in Rust requires third party packages and macros to sort out all boilerplate code?
- Rusky 3y agoNot really what I'm talking about here- that issue comes up when you want to combine multiple concrete error types in a single monomorphic call site, rather than when dealing with polymorphism. Rust (and other languages that use something like `Result` for errors) handles the polymorphic case a bit better than Java, because you can already use the language's usual polymorphism support for the error type. But it's still not quite as smooth as first-class polymorphic checked exceptions would be, since the interface (or trait) and its callers have to do a bit more manual plumbing in some cases. For example, a simple `impl Fn() -> T` can be instantiated with `T = Result<X, E>`, but only if the caller is just going to return the `T` directly- otherwise the error won't be propagated immediately. A slightly more annoying situation is when you have some `I: Iterator` that can fail- often you fall back to `I: Iterator<Item = Result<X, E>>`, which is not quite right, and expect the consumer to stop calling `next` if it gets an `Err`. With polymorphic checked exceptions, you could use `I: Iterator<Item = X>`, with an additional annotation that `next` may fail with an `E`. Error-oblivious combinators like `map` or `fold` would continue to work directly with `X` values, but automatically propagate `E`s to the eventual caller that knows the concrete type of `I`. (And again, crates like anyhow/thiserror don't really address this problem- they're solving a different issue entirely.)