4 ms·
Checked exceptions (like Java) are isomorphic with Rust's Result type. You could convert a checked exceptions syntax into Result exception-carriers on each retu
by barrkel 7y ago
Checked exceptions (like Java) are isomorphic with Rust's Result type. You could convert a checked exceptions syntax into Result exception-carriers on each return with a mechanical transformation, of a Rust+exceptions program into Rust-today program.
- Sharlin 7y agoAnd nobody, nobody, likes checked exceptions. Perhaps with appropriate syntactic sugar they could be made ergonomic, but Java's checked exceptions at least were a failed experiment.
- tomp 7y agoJava's checked exceptions are a failed experiment because of subclassing (i.e. eventually you get `throws Throwable` which doesn't give you any useful information). More precise exception tracking is essentially isomorphic to current Rust (Result etc.).
- Quekid5 7y ago(Just adding a bit to that:) It's not just subclassing. It also breaks with higher-order functions, e.g. what is the throws clause of a map() method on lists? There's really no good answer in Java, so what people do in practice is to just re-wrap into a RuntimeException, but that leaves callers with a problem: Calling code cannot match on exception types using a catch clause on the checked exception -- it has to check for a RuntimeException with an embedded checked exception. This is disastrous for ergonomics and real-life use of checked exceptions. The Result/Either method leads to its own ergonomic problems, but I think they could be solvable with support for row-types and (anonymous) union types. At least in Haskell, the Either method leads to a proliferation of FooError types whose only purpose is to wrap other error types. Well, either that or you accept that every little thing can return in a generic 'AppError'.
- barrkel 7y agoThe "right" thing in Java would be to parameterize map() by the error type declared on the lambda passed in. In other words: interface Function<T,U,Err> { U apply(T value) throws Err; } interface List<T> { <U,Err> List<U> map(Function<T,U,Err> f) throws Err; }
- lmm 7y agoThat only works for exactly one exception type. Err cannot be no errors, nor can it be two or more unrelated exceptions. You end up having to have n different overloads (for 0, 1, 2, ... exceptions) for every functions, and since Java's type system is not up to "varidic generics" you have to write them all manually.
- Nullabillity 7y agoYou could solve that with sum types (for 2+ exceptions) and never types (for 0),
- lmm 7y agoIt's not actually a sum, it's an inclusive union which are much harder to reason about and compromise your type system more (IMO). In any case, Java does not have any of those, and this is what makes checked exceptions impractical to work with; Rust has sum types but I don't think it has either inclusive unions or never types.
- Nullabillity 7y ago> It's not actually a sum, it's an inclusive union Right, whoops. My bad! > In any case, Java does not have any of those, and this is what makes checked exceptions impractical to work with Absolutely. I was approaching it from the standpoint of "what would I change about Java's type system to make checked exceptions usable?". > Rust has sum types but I don't think it has either inclusive unions or never types. Rust is working on stabilizing a true never type[0], and until then you can emulate it with an empty enum.[1] There is also a RFC for unions, but that doesn't seem to have gone anywere.[2] [0]: https://github.com/rust-lang/rust/issues/57012 https://github.com/rust-lang/rust/issues/57012 [1]: Such as https://crates.io/crates/void https://crates.io/crates/void which also provides helpers for safely coercing `Void` `Result`s [2]: https://github.com/rust-lang/rfcs/pull/1154 https://github.com/rust-lang/rfcs/pull/1154
- wtetzner 7y agoWhat benefits are there to checked exceptions over Rust's `Result` type?
- barrkel 7y agoAutomatic propagation without relying on macros like try!, or match with return on Err case. As you make it easier to propagate the error case to the caller, you asymptotically reach checked exceptions.
- wtetzner 7y agoI guess, although I'm not convinced that's an advantage. The ? syntax is pretty convenient, and it still allows you to do manual matching if you have a good reason to. I think the main thing you'd gain with checked exceptions is being able to list the possible failures in the function signature, instead of having to create an enum type. But I guess I'm not sure it's really worth the tradeoff.
- Rels 7y agoStrack traces? I love Rust error handling, but the one thing I sometimes miss is that there is no stack traces by default on errors.
- roblabla 7y agoThat's orthogonal to exceptions though. Errors could easily have stack traces automatically added to them (and IMO, they should).
- akavi 7y agoNot all isomorphic representations are equally ergonomic. Exceptions require special additional syntax, Result is “just” another value.
- barrkel 7y agoEnsuring error values get all the way to the top-level error handling loop is generally more ergonomic with exceptions, not less. It's easier for values to fall by the wayside, and for errors not to propagate when they're paired (type-wise) with values. If you do a lot of error handling close to the point of the error condition being found, the Rust / checked exceptions approach works well. For the kinds of applications that are written in Java, it's not the case. Most error conditions need to be propagated and the action in flight aborted.
- 0815test 7y ago> Most error conditions need to be propagated and the action in flight aborted. That's exactly what the '?' operator does. It's just as ergonomic as Java exceptions, and it doesn't hide the places where errors can be returned and the execution flow can be diverted.
- lmm 7y agoSuppose you have fn foo(i: i32) -> string throws IOException let x: Vec<i32> = ... let y = x.iter().map(foo).collect() What's the type of y ? There are no good answers here: if it's Vec<string> then where did the errors go? If it's Vec<Result<IOExecption, string>> then the user is pretty confused about where the Result came from. You can't propagate the IOException through the map() call in the general case (it might be in a third-party compiled library etc.). In Java this is a compilation error and the reason checked exceptions are useless in practice.
- barrkel 7y agoIn Java, you'd expect map() to throw, and the type of y to be Vec<string>, in Rust parlance. If one of the conversions failed, you'd expect the stack to unwind as the exception propagates, and you'd expect that the use of map() means you're not particularly interested in which item failed, you just want to abort (roll back) on failure. This is the 99.9% use case for exception in Java, or C#, or Delphi; you almost never catch exceptions. If you're interested in which item failed, ideally you'd be using a method that doesn't throw at all, and instead returns an error condition. I like how C# makes this clear, with Parse and TryParse on Int32. You use Parse when you want unwinding and abort behaviour including stack unwinding, and TryParse when you want to handle errors, and you don't use exceptions for expected errors, since expected errors are not exceptional. (If the map is lazy, you'd expect the materialization in collect() to throw. But the return type wouldn't change.) IMO, the kind of code you write on a daily basis is the primary determinant of your preference for error codes vs exceptions. If you need to handle error conditions frequently, you'll prefer error codes, because they're data, like all other data, and you can use the general compositional tools at your disposal to manipulate them. If you handle error conditions exceedingly rarely, and mostly just want to abort, unwind, roll back, go back to the main loop and log them, then exceptions are your friend. I do not think there is a fundamental superiority either way. I think there are tools for purposes. I do think that Java's checked exceptions are half-baked; they're half-way into the compile time type system in a language whose applications are generally better suited to run time exceptions.
- lmm 7y ago