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.
Top comments (7)
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.
Ey, that's cool! I never really used C++, but it looks a lot like Rust :D
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.
There is an IntelliJ Rust plugin AFAIK, and I use vim either way.
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
In rust you have the ? Operator is pretty amazing
Definitely, if you want to combine all errors, that's a pretty amazing operator/macro.