Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I've been using Go heavily over the last five years, starting with 1.2. Whatever warts the language has in its design, the error handling being one, it's still an excellent language for getting work done quickly, and correctly. I'm far more productive in it than Java, C++, or Python, and I'm old enough to have done lots of Pascal, Lisp, and more scripting dialects than I can count.

Having been writing software over the 30 years that you mention, it's clear to me that Go has not been isolated from language design, it's just made a choice to stay simple. Java, C++ and Python have exceptions to keep you from littering your code with error handling, and code with exceptions is hard to read. The line of code you are looking at any point in time may instantly jump to another place, effectively, having "goto"'s evil cousin, the "comefrom". Somewhere, in your code, is a "comefrom" statement which runs immediately after the statement you are looking at, you're not aware of its existence. My Java/Python for production code ends up looking a lot like Go in terms of error handling, because you have the most context about an error at the point where it happens, so that is the best place to deal with it. I believe that Go 1.x will have the staying power of C.



> Java, C++ and Python have exceptions to keep you from littering your code with error handling, and code with exceptions is hard to read. The line of code you are looking at any point in time may instantly jump to another place, effectively,

Rust and Swift both manage to make error handling easy while keeping this nice "errors are just values" property. Go could easily have added Rust-like or Swift-like error handling backwards compatibly with their existing idioms without falling back on weird special cases of format strings, but have chosen not too.

This does not make the language simple (even though in other ways it is).


How is rust error handling better than gos?


Okay, I'll bite.

It's better in many ways.

Firstly, rust has sum types, so it's possible to have exhaustive matches and know you've handled every case. This isn't possible in go. For comparison:

    // go
    val, err := doSomething()
    switch err.(type) {
    case *SomeErrorType:
      // handle
    default:
      panic("inexhaustive error check (at runtime, only if that error type is hit)")
    }
    
    // rust
    let (val, err) = do_something()
    match err {
      Err::SomeErrType(inner) => { /* handle */ },
    }
    // won't compile if the match isn't exhaustive
Note as well that you have to return the error interface, not some more specific type, in go because of the "nil struct is not a nil interface" gotcha. Juggling around structs that are returned as errors is basically impossible to do safely, so everyone returns the error interface. This is another way the language causes error handling to be worse.

Next, generics in rust allow for nicer chaining of computation in the presence of errors. Let me show you two examples. Again, the go and rust code is as identical as I can make it:

    // go
    val1, err := computation1()
    if err != nil {
      return nil, err
    }
    val2, err := computation2(val1)
    if err != nil {
      return nil, err
    }
    return computation3(val2)

    // equivalent rust
    computation1().and_then(computation2).and_then(computation3)
    
    // also equivalent rust
    let val1 = computation1()?;
    let val2 = computation2(val1)?;
    computation3(val2)
The ability to have a generic 'Result<T, E>' type to chain computation allows for code to be more readable, while still having all the benefits of errors being values.

The ability to have a generic 'Option<T>' instead of 'nil' also is very helpful, but enough has already been written about null pointers that I don't wish to rehash it here.

Finally, in practice, rust's higher level features (namely macros) allows libraries to create very powerful developer abstractions around errors, like those offered in the 'failures' crate, all without having any runtime overhead.

In practice, all of these things also combine to result in libraries offering better error types and allowing callers to handle errors well.


> The line of code you are looking at any point in time may instantly jump to another place,

It's the same in golang, any line can panic.


Aren't you supposed to check all the possible errors in golang anyway? How's that any different than java code being littered with error handling code?


Except it's possible to miss checking errors in golang (accidentally either by not assigning the return value, or by overwriting a previously assigned error). Whereas in a language with exceptions, this is not possible unless by explicitly adding code to ignore exceptions.

Do you ever see golang code that handles errors returned from fmt.Println?


I think I agree with you - I'm just trying to understand the other perspective from someone that seems to think not having exceptions is an improvement. I've mostly worked using java and go and to be honest I think I prefer exceptions because you should (almost) always check error codes in go anyway, and if you forget, you just made debugging much more difficult.


Yes, we're on the same page. It just seems that people parrot what golang authors say without actually deeply thinking about it.


I keep checking in on Go, trying to decide if I want to add it to my toolkit, but this argument usually bugs me.

Lisp is simple. Go is half-simple, but in its take on simplicity, it also takes away some constructs that make useful, high-leverage expressiveness impossible.

Some of its other benefits are appealing to me, but that's not one of them. It seems simple on its face only.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: