loading...

Comparing error handling in Java, Go and Rust.

legolord208 profile image jD91mZM2 ・2 min read

Error checking is everywhere. Sometimes it's more complex than other time. Take file managing for example. You have to remember to close the file every time you return... If it's not automatic.

Let's see how Java does it. First, we'll look at Java 6-.

BufferedReader reader;
try {
    reader = new BufferedReader(new FileInputStream("test.txt"));
    // read file
    if (condition) {
        // closed by finally block
        return;
    }

    // closed by finally block
} catch(Exception e) {
    // do proper error handling
    // closed by finally block
} finally {
    try {
        reader.close();
    } catch(Exception e) {
        // do proper error handling
    }
}

System.out.println("File read");

Oh my God. OK, that is HORRIBLE. Let's see how Java 7+ does.

try(BufferedReader reader = new BufferedReader(new FileInputStream("test.txt"))) {
    // read file
    if (condition) {
        // automatically closed
        return;
    }

    // automatically closed
} catch(Exception e) {
    // do proper error handling
    // automatically closed
}

System.out.println("File read")

That is... very good. Congrats, Java. Let's see how Go does it.

file, err := os.Open("test.txt") // no semicolon :(
                                 // i love semicolons :(
if err != nil {
    // do proper error handling
}
defer file.Close()
// read file
if condition {
    // closed by defer
    return
}

fmt.Println("File read")
// File closed on return, so you probably wanna return ASAP

One thing to note with Go: Independent error handling. You can't handle all errors clumped together. This is GOOD. This let's you specify where exactly it went wrong. Errors in Go are also pretty much strings. You make a custom error with errors.New("oh no"). Good stuff
Now let's take a look at Rust.

{ // Start a new scope. You don't *have* to do this
    let file = match File::open("test.txt") {
        Ok(file) => file,
        Err(err) => {
            // do proper error handling
            return;
        }
    };
    // read file
    if condition {
        // out of scope - automatically closed
        return;
    }

    // out of scope - automatically closed
}

println!("File read");

After writing this, I had to look at the code again, thinking "that's it".
Like you can see, I clearly like Rust's the most. Java 7's is also good. Go's is good, but defer statements only execute on function end, not scope based. You can of course close it manually, but that sucks too.
On the other hand, Go error types are amazing to work with. errors.New("custom error") is amazing. Java's is not too bad, but Rust's is "worst" for us lazy people, but also the most powerful. Plus there are macros that fix this.

Discussion

pic
Editor guide
Collapse
rkfg profile image
rkfg

Same in C++, it's called RAII and it's a great idiom. I like that it works well with exceptions so you can just throw and be sure the destructors will be called. And it can be used with whatever thing that needs cleanup, you can call anything a "resource", be it a database connection, reference counter or a text box scroll position (yes, I used RAII once to save/restore the scroll position). In Java it's not that flexible though, you have to explicitly markup the "RAII scope" using try-with-resources but that's understandable because GC contradicts with explicit lifetimes. In C++ it works in absolutely any scope.

Collapse
legolord208 profile image
jD91mZM2 Author

Ey, that's cool! I never really used C++, but it looks a lot like Rust :D

Collapse
rkfg profile image
rkfg

They're getting closer though Rust has the borrow checker and lacks a modern IDE. For C++ I use Eclipse CDT, it's almost perfect. Overall, C++14 (as 17 is not yet supported everywhere) is pretty good, it has almost everything you'd expect from a modern lanugage like lambdas, type inference (including function parameters type in declaration!), memory effective "move semantics", automatic memory management with smart pointers, null safety and so on. I get segfaults even less often than I get NPEs in Java! Some bits and pieces like coroutines and advanced date-time operations are covered by Boost. Unlike many other languages, with C++ you'll get C libs support for free and that's a lot of useful code. Give it a shot, it's worth it.

Thread Thread
legolord208 profile image
jD91mZM2 Author

There is an IntelliJ Rust plugin AFAIK, and I use vim either way.

Collapse
roddi profile image
Ruotger Deecke

If you look at Swift you will see, that it has all all the mechanisms above. defer {} for the cases where RAII is just to much boiler plate code. Enums with associated types for wrapping results together with errors (unfortunately the standard library is not there yet) and a try-catch mechanism that has the added bonus that it uses error objects instead of exceptions and the compiler enforces catching in the current context so you don't have exceptions flying all over the place.

Actually I don't think you can generally say that one concept is better than the others for all cases. As always: trade-offs

Collapse
geekcoders profile image
Michel

In rust you have the ? Operator is pretty amazing

Collapse
legolord208 profile image
jD91mZM2 Author

Definitely, if you want to combine all errors, that's a pretty amazing operator/macro.