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

You mentioned Rust: well, Rust has the question mark operator for this case, so you can automatically unwrap a Result<T, E> and return the Err<E> with only one character of code. I worked with Go for years, and it's just not true to say that Go's error handling is not unusually verbose.


Maybe that's just me, but I find the explicit returns easier to read.


A thing that helps is that syntax highlighters normally color the question mark differently, helping it stand out


Passing just the error up the stack is also quite easy in Golang, just use a named error return value and assign to it:

    func test() (result interface{}, err error) {
        _, err = someOtherFunction()
    }
The thing is, you probably want to handle the error eventually, and that is just as verbose in Rust [1] as it is in Golang (IMHO). One can of course debate if it's more pleasing to use a "match" operator than an if/then/else loop to handle errors, but to me the difference is marginal. BTW one could even mimick Rust's type-based errors in Golang by returning a single interface{} type that's either a result or an error and then using a type switch, but that seems just silly and I have never seen that in the wild.

1: https://doc.rust-lang.org/rust-by-example/error/multiple_err...


A stack of ten functions wants to handle the error once. Returning it nine times is exactly the type of mind-numbing chore that computers do much more quickly and reliably than people, and I will never understand why so many want not to automate this.


Returning an error through nine levels of functions without doing anything with it anywhere in that call stack would indicate a strong problem with the code architecture to me. Personally I rarely pass on errors "as they are" in Golang anymore, as that makes debugging quite hard. A function receiving an error should either handle it (e.g. by recovering from it or logging it), or at least add relevant context to it so that the user can figure out what's wrong.

I used to like passing errors up the stack like you describe, but this invariably leads to hard-to-debug programs that terminate with unspecific errors like "i/o error", leaving it to the user or developer to figure out in which exact branch of the 9-layer call stack that error originated. If your error handling is exception-based and if your interpreter/runtime generates tracebacks you can do that, but not if you explicitly handle errors within the regular control flow.


Wrapping at every layer usually ends up with:

  failed to start a session: failed to read the session config file: i/o error
This is perfectly equivalent to

  java.lang.IOError
  in startSession() line 192
  in readConfig() line 123
  in openFile() line 131
Except that with Exceptions you get this for free, with more context than naive wrapping (and much more than `if err != nil { return err }` ).

Usually with exceptions, you want to catch and add more context at every layer boundary, and get the call stack for free.


The only thing I'm missing there is the filename, otherwise this is a helpful error message since it tells the user what the problem is and at which stage the error occurred. Stack traces, on the other hand, are mostly only helpful to the original developer(s) of a program, they won't help most ordinary users. And while they of course include the calling context they usually don't include other crucial information like variables (e.g. the filename in the example above).

Also, raising and catching exceptions is expensive in many languages. I know that in Java it's fast, but other languages like Python produce significant overhead when creating and raising an exception, that's something one should consider as well.


All you're arguing, then, is that regardless of whether you use error values or exceptions, you need to write good error messages. Hmm, okay. I agree with you on that. But I don't see how that counts against exceptions. Do you think the error message of an exception cannot contain filenames and variable values? And even in cases where they don't (e.g. you're depending on an external library with poor error messages), do you think it's impossible to add context to an exception's error message after it's been raised?

Also, the performance argument counts against your point, not for it. Exceptions rarely happen (when used properly), and when they are not raised, they cost nothing. Whereas go-style errors require explicit checks every single time you execute an operation which can fail.


> this invariably leads to hard-to-debug programs that terminate with unspecific errors like "i/o error", leaving it to the user or developer to figure out in which exact branch of the 9-layer call stack that error originated

You might want to add a caveat to 'invariably', something like: 'provided you're using a language where errors are modelled as one big mashed-together string' ;)

If we assume the basics of how Go is designed are an invariant, I think the best change would be adding syntactic sugar to return a struct satisfying the error interface, annotated with the (statically encoded) symbols and line numbers, and then also the arguments for those function calls. I have never seen a Go codebase whose hand-written 'contextual' error handling blocks provide even this, let alone more useful information. If you have, I'd be interested to hear about it!


Since Go 1.13 wrapping errors is part of the standard library [1]. You could use the "runtime" package to annotate errors with information about the calling context, but IMHO that's not a good idea. I think stack traces should be reserved for unexpected errors, and that is already well covered by the "panic" mechanism. Potentially expected errors that can be handled or returned to the user should simply provide enough information to know what went wrong without forcing the user to go through the code.

1: https://go.dev/blog/go1.13-errors


I see value in wrapping the error at the source, but I don't see the value in wrapping it beyond that point, unless there's there's genuinely new context to add at some boundary.


Now that computers are starting to write software it will eventually matter less. Until then, code is written by and for people, not computers.


The difference is when a function has more than one line.

In go, you constantly have to `if err != nil { return }` even with named return values. This is the part that breaks up the flow constantly and hurts readability.

In Rust, the ? macro also handles this.


Precisely. As soon as you go beyond a ten-line toy program, you'll hit this problem, and in my experience it's almost universally acknowledged to be a problem with Go.

Rust's '?' sugar effectively replaces the whole `if err != nil { ... }`, meaning it wasn't really honest to list Rust as another language that proves that error handling just has to be verbose and repetitive.


See my comment below. I never use this pattern personally, just wanted to point out that it exists. In general I think passing up errors through the call stack without adding context to them or handling them is a strong anti-pattern. I think many people in the Golang community see this similarly. And by passing an error up the stack with "?" you haven't magically "handled" it, so I don't think that's a fair point.


I'm not sure how that code block is meant to work. You still need to check the value of error and conditionally return, unless you're suggesting that most functions only contain one function call.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

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

Search: