Remix.run Logo
xmodem 2 days 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 a day ago | parent | next [-]

It 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 a day ago | parent | prev [-]

A 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

lock1 a day ago | parent | next [-]

Interesting 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"
Defletter a day ago | parent | prev [-]

Yeah, 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
            }
        }
    }