5 ms·
A hot take I have is that Java's checked exceptions - which everyone hates - are semantically very similar to Rust's Result<T, E> and ? error handling - which i
by xmodem 1y ago
A hot take I have is that Java's checked exceptions - which everyone hates - are semantically very similar to Rust's Result<T, E> and ? error handling - which is broadly liked. The syntax makes or breaks the feature.
- lock1 1y agoIt shares the same important property (short-circuiting, explicit & type checkable) with Rust's `Result<>`, yes. I don't think it's a direct equivalent though - Java's exception creates stack trace by default - Type erasure which prevents you from doing anything "fancy" with try-catch + generic's type parameter - You can only throw something that extends `Throwable` rather than any type `E` - etc But yeah sure, if Java somehow provided a standard interface & an associated operator like Rust's Try trait to handle checked exception, it would probably have a much better reputation than it has now.
- redditor98654 1y agoA while ago, I had attempted to do it the Rust style with a Result type in Java (where it is called Data oriented programming) and the result is not great. I was fighting a losing battle in a naive attempt to replace checked exceptions but still retain compile time type safety. https://www.reddit.com/r/java/s/AbjDEo6orq https://www.reddit.com/r/java/s/AbjDEo6orq
- Defletter 1y agoYeah, Java's generic-type erasure makes result types difficult, though as you mention in your code example, this can be mitigated some using switch guards. But you could also go into another switch: switch (result) { case Ok(var value) -> println(value); case Err(var ex) -> switch (ex) { case HttpException httpEx -> { // do something like a retry } default -> { // if not, do something else } } }
- lock1 1y agoInteresting anecdote. At $DAYJOB, I'm currently migrating a non-performance sensitive system to a functional style, which requires defining basic functional types like Option<> and Result<>. So far I think it works pretty well and certainly improves edge case handling through explicit type declaration. My current stable incarnation is something like this: sealed interface Result<T,E> { record Ok<T,E>(T value) implements Result<T,E> {} record Error<T,E>(E error) implements Result<T,E> {} } Some additional thoughts: - Using only 1 type parameter in `Result<T>` & `Ok<T>` hides the possible failure state. `Result<Integer>` contain less information in type signature than `Integer throwing() throws CustomException`, leaving you fall back on catching `Exception` and handling them manually. Which kind of defeats the typechecking value of `Result<>` - A Java developer might find it unusual to see `Ok<T,E>` and realize that the type `E` is never used in `Ok`. It's called "phantom types" in other language. While Java's generic prevents something like C++'s template specialization or due to type erasure, in this case, it helps the type system track which type is which. - I would suggest removing the type constraint `E extends Exception`, mirroring Rust's Result<> & Haskell's Either. This restriction also prohibits using a sum type `E` for an error. - In case you want to rethrow type `Result<?,E>` where `E extends CustomException`, maybe use a static function with an appropriate type constraint on `E` sealed class Result<T,E> { public static <E extends Exception> void throwIfAnyError(Result<?,E>... ); } - I like the fact that overriding a method with reference type args + functional interface args triggers an "ambiguous type method call" error if you try to use bare null. This behavior is pretty handy to ensure anti-null behavior on `Option.orElse(T)` & `Option.orElse(Supplier<T>)`. Leaving `Option.get()` as a way to "get the nullable value" and a "code smell indicator"