Shared publicly  - 
 
I've gotten a fair amount of mail about a recent blog post titled "Why I'm not leaving Python for Go" [1], in which the author says that Go is great except that "errors are handled in return values". I thought it would help to write a little about why this is.

In Go, the established convention is that functions return errors; they don't panic. If a file isn't found, os.Open returns an error; it doesn't panic. If you write to a broken network connection, the net.Conn's Write method returns an error; it doesn't panic. These conditions are expected in those kinds of programs. The operation is likely to fail, and you knew that going in, because the API designer made it clear by including an error result. 

On the other hand, there are some operations that are incredibly unlikely to fail, and the context is such that there is no way to signal the failure, no way to proceed. These are what panic is for. The canonical example is that if a program evaluates x[j] but j is out of range, the code panics. An unexpected panic like this is a  serious bug in the program and by default kills it. Unfortunately, this makes it difficult to write robust, defensive servers that can, for example, cope with the occasional buggy HTTP request handler while keeping the rest of the server up and running. To address that, we introduced recover, which allows a goroutine to recover from a panic some number of call frames below. However, the cost of a panic is the loss of at least one call frame from the stack. We did this intentionally. To quote from the original mail: "This proposal differs from the usual model of exceptions as a control structure, but that is a deliberate decision.  We don't want to encourage the conflation of errors and exceptions that occur in languages such as Java."

The post I mentioned at the start asks "why is an array out of bounds any more cause for panic than a bad format string or a broken connection?" The answer is that there is no in-band way to report the error during the evaluation of x[j], while there is an in-band way to report an error in a bad format string or a broken connection. (The format string decision is interesting by itself but orthogonal to this discussion.)

The rule is simple: if your function is in any way likely to fail, it should return an error. When I'm calling some other package, if it is well written I don't have to worry about panics, except for, well, truly exceptional conditions, things I shouldn't be expected to handle.

One thing you have to keep in mind is that the target for Go is programming in the large. We like to keep programs concise, but not at an increased maintenance cost for big programs worked on by large numbers of programmers. The seductive thing about exception-based error handling is that it works great for tiny examples. But diving into a large code base and having to worry about whether every single line might, in ordinary operation, trigger an exception worth handling is a significant drag on productivity and engineer time. I have had this problem myself finding my way around large Python programs. The error returns used by Go are admittedly inconvenient to callers, but they also make the possibility of the error explicit both in the program and in the type system. While simple programs might want to just print an error and exit in all cases, it is common for more sophisticated programs to react differently depending on where the error came from, in which case the try + catch approach is actually more verbose than explicit error results. It is true that your 10-line Python program is probably more verbose in Go. Go's primary target, however, is not 10-line programs.

Raymond Chen's articles are the best exposition I've seen about the pitfalls of trying to do error handling with exceptions:
http://blogs.msdn.com/b/oldnewthing/archive/2004/04/22/118161.aspx
http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx
Suffice it to say that the Go developers agree: error is so important we made it a built-in type.

Russ

P.S. Occasionally you see panic and recover used as a kind of non-local goto, similar to longjmp and setjmp in C. This is fine too, but only as an internal detail of your package. If callers need to know, you're still doing it wrong.

[1] http://uberpython.wordpress.com/2012/09/23/why-im-not-leaving-python-for-go/
218
87
Ralph Corderoy's profile photoSaurav Sengupta's profile photoChainsinghchodrislm Chainsingh's profile photoAlbert Strasheim's profile photo
71 comments
 
Error handling by return values have their own drawbacks, though, the most notable of which is people neglecting to handle the error at all - either because they think it's never going to happen or because they're simply lazy (I've been in both groups in the past).

Do you have any plans or ideas on avoiding those pitfalls in Go?
 
i saw this article as nothing more than the Obligatory Contrarian chiming in, because its easy pageviews on hackernews. and i think it is time to tell the exception advocates to just fork the language if they are that adamant about it.
 
Perhaps just another argument of an exception fanatic...
 
I like Go's approach to errors.  I do get a little tired of reading/writing this so often:

  if err != nil {
    return err; // or, less often, panic(err)
  }

I've not found a good way to factor out this kind of boilerplate. Have I missed something?
 
+Michael Hendricks You'd need a macro to kill that boilerplate for return, but for panic, I write a lot of things like `maybePanic(err)` or `maybeFatal(err)` for CLI tools and what-not.
 
Sorry to say it, but while reading this post all I could think was perl eval/die.
 
For me, the basic boilerplate for Go error handling has long since become second nature.
 
I think Russ is mostly right about the tradeoff between exceptions and "returning" errors:  If the event is not truly exceptional, then don't use exceptions.

However, I also think that Go's approach is indeed flawed in that it makes it too easy to ignore the error result.  The presence of a possible error result should be evident in the type, and the only way of getting at the normal (non-error) result should involve a check for the error.  In other words, make the result a sum type, combining a normal return value with an error value.  If all you need is "there was an error", then something as simple as ML's option or Haskell's Maybe type will do.

Of course, there is a deep connection: together with the "normal" result exceptions form sums where the "exceptional" portion gets propagated automatically, i.e., where every operation is already "lifted" into the corresponding monad.  Indeed, Haskell's Maybe is also a monad.
 
It's interesting Java supports the same explicit error handling through checked exceptions which no other language after it mimicked. Those exceptions also couldn't just silently bubble up the call stack unless you added them to each calling function signature.

What's nice about try catch though is that you can consolidate the error handling of several of the same exceptions in once place:

try {
  trySome(); // throws CheckedException;
  
  trySomeMore(); // throws CheckedException;
} catch (CheckedException e) {
  // handle error
}

Is there a way to do this in Go without checking each error return value? A counter argument would be that it's not clear from looking at the code which of the methods can actually throw CheckedException and the explicit nature of Go yields better legibility.
   
 
+Felix Berger the one time I thought I needed something like that, I used panic/recover.  Afterwards, I restructured my program and made it much cleaner and significantly faster (panic/recover has some overhead, but the thing I was doing that required it did as well).

I've written some significant apps in go recently and error handling hasn't been my biggest challenge (mostly, doing stupid things that couldn't possibly work regardless of error handling strategies has slowed me down a few times).
 
"It is true that your 10-line Python program is probably more verbose in Go." Shouldn't that be s/more/less/?
 
Seems to me Go aligns itself with at least *ix.  Most system calls return either a result, or NULL, NULL pointer, negative value, or whatnot on erroring.  Doing things like going off the end of an array raises SIGSEGV, SIGBUS, or something like that.  So implementations on *ix would feel right at home with returning errors + "panic" (raise a signal) on really serious flaws.

Still...I do miss the days where I used to write:

50 ON ERROR GOTO 10000

where all program misdeeds could be handled in one place, especially just to close up things, print out what went wrong, and terminate the program in one place.
 
+Felix Berger beat me; the parallel with Java's checked/unchecked exceptions was also obvious to me. But if you agree, then I wonder if Go will also be criticized by the same reason that Java's checked exceptions are: namely, it's sometimes hard to decide which errors are expected and can/should be handled by application code, and which ones are not and should silently propagate to a process termination or maybe to some master handler like a request-processing loop. Because every time you make the wrong choice, especially in important APIs that cannot be changed after released, you get a generation of programmers hating you. In the Java ecosystem this resulted in almost everybody deciding that checked exceptions were a failed idea, which IMHO is throwing the baby out with the bathwater.
 
I suppose one could add a variation of the "comma OK" construct that triggers a panic on an error return by having a different placeholder where underscore would be used to ignore the error.  It would replace the "if err != nil" test that so many consider verbose.

(Hm, a channel construct of some description?  That would certainly be able to aggregate errors!)

I believe that being able to test whether an error has occurred is a choice that should only be taken away from the programmer when there is no benefit whatsoever from it.  That the programmer then may decide to make a bad choice is not a language concern, but a management one.  The reverse gives management no option at all.
 
It's educational to compare Python's subprocess module and the CalledProcessError raised by subprocess.check_call to Go's os/exec package.
 
Part of the problem in this discussion is that most exception systems conflate two things: signalling an exception and unwinding the stack. In Common Lisp, you can choose to run exception handlers in the context where the problem occurs. For a contrived example, let's say division by zero raises an exception. If you establish a use-value restart, you can at top level choose to let the computation continue with 42 as the result of the division. 
 
The exception consolidation mechanism can be emulated with error-return-values if you specifically want blanket-handling across a series of statements, though it doesn't particularly fit into the Go style. Conversely, common exception handling mechanisms in other languages can't do fine-grained error handling without a lot of verbosity; usually it's not important in those languages, but for some tasks, it's critical.
 
+Nick Johnson  If you write some Go code, you will find out this simply never comes up in Go, I can't remember a single time where i have seen this be a problem for anyone writing Go code. The multiple value returns plus the no-unused-vars allowed rule take care of most cases where this would be a problem. If somebody really worried about this, it would be trivial to write a go vet rule that notified you of any unassigned return values of type 'error', the only reason as far as i know why nobody has done it is because it has not been a problem for anyone.
 
I do find the divide between people unwilling to try something at all because of a prejudice and people getting lots of stuff done in it quite amusing.
 
"In rebus mathematicis errores quam minimi non sunt contemnendi."
Translate
 
+Uriel Étranger Really? It's some time since I wrote anything in Go, but if I recall correctly, Go doesn't force you to assign the return value of a function to anything.
 
I hadn't thought about it, but it seems to me that whenever a Go function needs to return an error condition, it may as well return one or more results too, in which case the assignment to the error code can only be intentionally omitted. A useful side-effect.
 
+Nick Johnson it forces you not to have unused variables, when (as most functions that error) a function returns two values, you have to assign both of them, or explicitly 'ignore' one of them using _.

As I said, it sounds like a problem in theory, but it simply does not come up in practice, is a complete non-issue. (And again, if you wanted to write a tiny extension to go vet that checked for unassigned returns of type error, you could do it trivially, but nobody has bothered because nobody has found it to be a problem at all.)

Sorry if I sound too dismissive, I can see how one would fear that it is a problem, it just simply isn't, in my experience or the experience of anyone I know writing Go code.
 
+Uriel Étranger Plenty of functions that can error have no return value other than the error. What about them?

It seems to me that while it may be harder to accidentally ignore error values for functions with return values, that's not the case for functions that don't return anything - which is exactly the problem faced in C.
 
1. What is an "in-band way to report an error"?

2. How do you deal with the problem of first determining and then maintaining checked exceptions throughout one or more programs, especially as the programs keep getting larger and more numerous?

3. Since Go is not a functional language per se, what conceptually differentiates a functional output, such as the typical factorial, and an error value returned from a function, such as trying to get the factorial of a negative integer from the same function? Or should something like attempting to get the factorial of a negative integer be classified as an exception that should trigger a panic?
 
+Nick Johnson I could try to argue with you, and even go case by case, and analyze existing codebases and on and on, but i really recommend you go back to write Go code again, you will find it is not a problem at all, trust me! ;) (Really, until this python post came up, nobody had even brought the issue, as i said, it simply doesn't come up in real world code.)
 
+Saurav Sengupta, 1A, things like open(2) returning -1 to indicate an error as valid file descriptors begin at zero, arguably stdio.h's EOF macro too.
 
+Michael Hendricks, I too find reading the commonplace err-checking tedious, particularly ensuring != has been used instead of ==, and wonder if type error should be allowed as well as bool, with only nil being false. That would allow

    if foo, err = bar(); err {
        return err
    }

Better still would be if this was allowed on one line as a trivial run of calls to A, B, C, would become more condensed and the pattern clear.

    if a, err = A(); err { return err; }
    if b, err = B(a); err { return err; }
    ...
 
+Ralph Corderoy Then why is there no "in-band" way to report an array index error? And how is throwing an exception or raising a signal "out-of-band", considering that for both return values and exceptions, both the called and the calling scopes are in the same position? The fact that one style allows a program to continue without checking for errors and the other terminates the program if an exception is not caught should not make a difference to the called scope. In both cases the responsibility for checking for failure lies with the caller.
 
We had a difficult-to-diagnose occasional failure in Gmail not too long ago that was ultimately traced back to a line of C++ that didn't check a status return. It would have been easier to diagnose in the wild if we had exceptions, causing an error to be propagated to the caller (even if not quite properly handled). And with good static analysis (whether "Error: status return must not be ignored" or "Failed to handle checked exception"), it never would have happened at all. So IMHO "Why I'm not leaving Python from Go" makes a good point.

I think languages should have three priorities in error handling:

1. It must be hard to completely swallow an error. (As in neither handling the subtle state details nor propagating it to the caller. This is a common failing of error returns.)

2. It should be hard to not know an error is being propagated to the caller. (This is a common failing of exceptions. Not knowing about it means you don't think about whether you're getting the state handling correct.)

3. It would be nice to be terse: not tripling the code size with error propagation.

Checked exceptions provide all three. Terse, static analysis to ensure you have thought about every type of error, although sometimes people do so in an overly broad fashion ("catch Exception").

Go's error returns provide nothing. But with static analysis to ensure you don't ignore an error return, they'd provide #1 and #2.

Behaviorally speaking, Raymond Chen's "bad error-code-based code" has no exception-based equivalent. His "not bad error-code-based code" and "bad exception-based code" are the same. And while he says that "good error-code-based code" is only "hard" while "good exception-based code" is "really hard", I'm unconvinced. They both require thinking about what should be done about an error after any line of code, ordering things carefully, etc. One is more verbose, but I'm not sure that makes it any easier. I suppose it means you see all the error handling when reading the code, not just when writing it, so arguably it improves #2, but the terse exception-based "throws FooException" gives a hint to look carefully at each call.
 
+Ralph Corderoy Yes and no. Yes, if in-band means the syntax of that statement. No, if exceptions can be handled in a similar manner. The point is that exceptions can be handled in similar syntactic forms. What syntax and semantics a language uses for exceptions is a different matter, and probably what gives rise to the question of handling out-of-the-flow situations in one way or the other. My take on this is that handling these conditions need not be an either-or case. The throwing and handling of exceptions can be made much lighter in both syntax and semantics than the way it is implemented in current mainstream languages. In any case, whether we use return values or exceptions, it does not make much of a difference in the effort that the programmer has to invest in both raising and handling errors and exceptions.
 
yeah, let's not have "comma error" assignment constructs for arrays and slices. If you did that, you might as well change "comma ok" map assignments to return errors instead, for consitency, or vice versa. Of course, indexing/slicing can be used in arbitrary expressions. How would you handle, in-band, something like:

  n, x := 25, []int{1,2,3}
  x = append(x[n:], 4)

I think illegal indexes are always worth a panic, as are divide-by-zeros, since it's always the programmer's fault, one way or another.
 
+Kevin Gillette Depends on exactly how you differentiate between the success of a map lookup and the success of an array/vector lookup.
 
How much benefit does a multi-value return carrying an error value payload provide over a traditional C-style error check, given that we have to ultimately check the error value in the former case anyway? Indeed, in that case, how much benefit do exceptions provide too (not the forced termination of a program)? In every case the actual error or exception handling requires similar programmer effort. One method lets a program run in an erroneous state while the other takes a Draconian measure to prevent that (leaving the OS or VM to deal with resource deallocation). Where is the real difference that actually lessens the programmer's effort?
 
+Saurav Sengupta Since exceptions are specifically "out of flow", it's not really clean to use the same syntax -- you could modify Go to do only panics instead of error returns (and what you panic with must be of type error), and make a final value assignment equivalent to an in-flow recover(). Take for example:
  
  somefunc := func() int { panic(ErrSomething) }
  x, err := somefunc() // equivalent to "try... catch"
  x := somefunc() // stack unwinds

This may be a clean syntax that would work as an alternative to exception languages that don't support multiple return values, though, it doesn't make code any shorter unless you want to ignore unexpected exceptions. In languages with multiple return values, you probably couldn't use an additional variable assignment (especially dynamic languages like Python), but would instead need alternate syntax anyway, such as `x, y, z : err = somefunc()`

C-style error checking is terrible. For one thing, iirc, it uses a global (if we're lucky, it's thread-local). More importantly, an extra return value of type error explicitly indicates that the function can fail. In C, you have to consult the documentation to see what can fail. In terms of programmer effort, if correct programs are desired, not having to read through documentation for every library function you use really lessens effort. Java is the only exception-style language I know of that doesn't have this issue when dealing with "checked" exceptions, since you cannot compile unless you either handle all exceptions that can be thrown to you, or you explicitly indicate your method can throw the exceptions you don't handle. In cases where error handling is up-front and visible (and where you must make a conscious decision to ignore errors if that's what you want to do), it lessens programmer effort because your system is much less likely to fail on a corner case, after running for six months (when you've long since forgotten what the code does).

It also sounds like you're presuming anything can fail in Go, which is certainly not the case; most of the functions in the strings package cannot fail (strings.ToUpper, for example), since you cannot pass anything but a string to the functions, and the functions correctly handle any string as per the defined behavior.

The size of a slice is completely under the control of the programmer, and it's trivial to ensure that correct indexing is used. The only overlap with maps would be if you had map[int]string, for example, to mimic a []string. In all other cases, it's fairly input-defined what keys are stored in the map, and the ability to check whether the key was present or not is critical.
 
+Kevin Gillette If you use checked exceptions as in Java, you have to determine up front what all possible exceptions the code can encounter, which is not always easily possible as I mentioned in my first comment, and it also makes the code incredibly verbose. On the other hand, if you use error values, whether through a global variable or through a return value, your program may keep running after an error if you have not checked it. That is why I say that the programming (not the documentation consulting) effort remains similar. And no, I am not considering everything potentially failing in Go. I am considering precisely those cases that can fail. If multiple return values (or special syntax) are used, we still have to ultimately check that value. Exceptions, on the other hand, become out of flow or out of band or whatever because of the way they are usually thought of and implemented in most mainstream languages. The point I want to make is that at the end of the day neither method makes a significant difference in the effort needed to handle the error. I have not yet come across any error handling method that creates that difference but that is mostly because of the non-spatial nature of programming which is another story. The one thing that can be done to make exceptions more "in-flow" is to change not just their syntax but their semantics as well.
 
+Saurav Sengupta If you had a possible error at array indexing it would be something like "if val, ok = arr[i]; ok { ... }". You can already do this: "if i < len(arr) { val = arr[i]; ... }". Failing to check this error condition leads to a panic (in the second example), which is very reasonable. The method to avoid the panic is at your disposal.
 
It wouldn't be too difficult to have a static analysis tool check if you ignore errors in single return functions. It could even be done in the language (should the authors decide to). That would be a simple change that would make people stop complaining about this (the possibility of ignoring errors). I would welcome such an improvement.
 
+Costin Chirvasuta, is there a race condition there that v, ok = a[i] would presumably avoid by design? (Not that I'm supporting the general idea.)
 
This is completely irrelevant to the topic of race conditions. All of these supposed syntax forms, as well as Go's current style do not protect against race conditions. Go leaves concurrency for the programmer to either explicitly manage with mutexes or other atomic primitives, or avoid by using channels.
 
+Ralph Corderoy I don't think comma ok is atomic in any case. Idiomatc Go usually takes away the burden of thinking about what operations are atomic. I wasn't talking about parallel code though.
 
+Matthias Blume A Sum type for the error return doesn't work for all situations (see io.Reader interface), it forces you to handle it because it makes the handling far more complicated.
 
Ways that significantly reduce programmer effort in error and exception handling are not yet mainstream, or perhaps don't even exist. It is one of the things that has had us trying this and that for years without really leading to something ground-breaking. Actually, I am busy creating a new programming language, so my motive here is to discuss and understand what programmers like and dislike about current error handling mechanisms.
 
+Jesse McNelis Yes, the point of using sums is to make handling the error return mandatory.  I don't know what you mean by your reference to io.Reader.  In which situations would using a sum not work?
 
+Matthias Blume many functions/methods, including implementers of io.Reader, need to access both the non-error return values and the error in some cases: just because there was an error, doesn't mean that zero data was read
 
+Uriel Étranger You claim that it's not a problem at all. Let's validate that, shall we?

The hello world example given at http://golang.org/ is likely the first piece of code that a Go beginner would see. It reads:

func main() {
fmt.Println("Hello, 世界")
}

That's interesting. Println is called without handling its return value. This pattern is repeated in every other part of the Tour. But the documentation at http://golang.org/pkg/fmt/#Println says:

"It returns the number of bytes written and any write error encountered."

So maybe the simplistic hello world program isn't idiomatic Go. Maybe you guys trust Println to never fail, and I guess that's fair since people do the same in C too. But then same problem appears again at http://tour.golang.org/#57, which uses http.ListenAndServe without checking for the error it returns, and at http://tour.golang.org/#60, which by using pic.ShowImage (defined at http://code.google.com/p/go-tour/source/browse/pic/pic.go) throws away the error returned by http://golang.org/pkg/image/png/#Encode. Shall I dig up some more?

So not only does Go allow programmers to ignore errors at their pleasure, it also seems to encourage them to do so via its main tutorial. And you're here telling me that it's not a real problem?
 
+Matthias Blume if you consider sum types and tagged "unions" to be equivalent, then you've just made the situation much more complex, since if there's an error, but there's useful data in it, you have to create two code/access paths to get to the same information (one path in case there was no error, and another path if there was.

+Kangyuan Niu all programming decisions need to be tempered with experience and systems knowledge. Yes, we gleefully ignore fmt.Print (and friends) errors, since in this case, there usually isn't much point in doing anything else, even in larger programs. What are we going to do? Write to stdout that stdout is broken/closed? And if stdout is closed, then chances are stderr is also, or at least nothing on the receiving end cares anymore, and the proper thing to do is quit silently. Depending on the situation, it may be easiest to just falsely pretend there's no possibility of a problem (by ignoring errors), especially if there are many places in which output occurs. In other cases, it's easier to check the error, and exit, if the program's only purpose is to write something to stdout (such as a classic unix text-stream utility).
 
+Kevin Gillette I already acknowledged the special case with fmt.Println, but there are two points to consider here:

1. If fmt.Println is, as you say, so unlikely to fail that people should ignore its return value, then why doesn't it employ the panic/recover mechanism as dictated by the heuristic described in +Russ Cox 's original post?
2. What about the other examples I mentioned? Did you read my entire post? Do http.ListenAndServe and png.Encode also belong in the class of functions that should never fail?

And your response to +Matthias Blume makes no sense. In any error handling mechanism, be it sum types or exceptions or error codes as in C or Go, there are "two code/access paths". Splitting the path of execution is exactly what pattern matching (for sum types), catch blocks (for exceptions) and conditional statements (for error codes) do. There is no "same information": if you got an error, the result is invalid; if you didn't, you only need to use the result.

Tagged unions are a specific implementation of the idea of sum types, by the way. I don't see what this colloquial substitution of terms has anything to do with whether there are multiple execution paths.
 
+Kangyuan Niu no. My analysis was that it's very likely to fail (piping it to the unix head utility is an easy way to force this), and the special case is that there's usually nothing you can or should do about it, unless you opt to exit the program immediately. This is why none of the corresponding log package calls return errors.

http.ListenAndServe is expected to fail (which is why it returns an error), especially in the case of the port already being bound, or in the TLS equivalent, if there's something wrong with the provided keys. png.Encode has a number of failure cases as well.

Regarding returning a single sum type value for both error and non-error cases, in lieu of multiple return values: if a read error occurs, io.Reader allows implementations of the Read method to return an error and a non-zero n in the same call, or return a zero n and a non-nil error in a subsequent call. For anyone using the Read method, that means they still have to assume that data may have been read even if there was an error. With Go as it is now, you do something like:

  buf := make([]byte, 4096)
  n, err := r.Read(buf)
  if n > 0 {
    // do something with buf[:n]
  }
  if err != nil {
    // do something with err
  }

As per the suggestion of +Matthias Blume, it'd have to be something like:

  buf := make([]byte, 4096)
  var n int
  var err error
  // treating sum types like empty interfaces with
  // explicitly constrained types.
  switch result := r.Read(buf).(type) {
  case int:
    n = result
  case error:
    err = result
    n = err.stuffed_data.(int)
  }
  if n > 0 {
    // do something with buf[:n]
  }
  if err != nil {
    // do something with err
  }

The sum-type version is essentially the same as the multiple-return-value version, except much longer. You might say that I put in redundant code, but with io.Reader, based on the way control flows (you often return the err if it's not io.EOF, passing the buck for someone else to handle), you pretty much always process 'n' before err -- the docs for io.Reader even mentions that this is needed for correctness. The sum-type snippet could be made shorter if the processing logic were simple and you stuffed that logic in defers; not a very clean alternative.

It's my estimation that the sum-type idea didn't initially consider that you often have to use all of the return values, not just either one or the other. If there were no cases where you needed more than one return value, then it would be an "okay" solution, but ends up being like what you have to do in C, Java, etc, when you want multiple return values: you make a struct or class (or union) to handle any of the possible return-value-type-combinations, and that number explodes so quickly, that you don't really want to do it anymore, so in my opinion, it's still a lot less elegant than multiple return values. I may not see the full scope of what the sum-type solution is trying to achieve, but if it's primarily to enforce what is perceived as a convention of "error or normal value, never both", then it's somewhat moot, since there are a great many canonical anti-examples. Yes, in cases where it's impossible to construct a usable "normal" value because of an error, then in those cases, it is typical to return a zero value along with the error.
 
That's just because this particular language sucks at expressing and manipulating sum types.
 
+Matthias Blume true. I wouldn't so much say it sucks at expressing and manipulating them -- I'd say it just plain doesn't have sum-types at all. I'm in favor of an experimental branch of Go toying around with implementing sum-types, but for completely different reasons -- for one, I think it'd make another subset of "generics" that people keep pining about reasonably doable. But still the point was that while first-class, convenient, expressive sum types may be good for handling mutually exclusive return value types, Go's return values certainly aren't mutually exclusive in the general sense, or even in the majority of cases, and so sum-types are entirely inadequate for the task.
 
+Kevin Gillette, your example is a non-sequitur. The problem with your thinking is that you see a dichotomy where there is none. If a product type is more suitable than a sum type for the value returned by Read, then keep it that way. Nobody's suggesting that we get rid of Go's product types, and adding sum types does not at all force you to use them everywhere. The Read example has nothing to do with whether sum types would be useful in other functions, like, say, the previously mentioned png.Encode. Even if they are not applicable in the majority of cases, why would you want to deprive me of the ability to use them in the remaining minority of cases? Would you say that floating point numbers are useless because there are "canonical anti-examples" in which plain integers are more suitable? Why would the addition of a way to compose types deprive me of Go's built-in "tuples" and force me create structs for multiple return values? This objection makes no sense at all.

And I see what you mean by clarifying the difference between "unlikely to fail" and "nothing to do even if it fails". Still, ignoring fmt.Println for a moment, the way those other functions are used in the Tour does encourage newbies to ignore errors for convenience, does it not? Or are you going to keep insisting that it's not a "real" problem? Do you trust your fellow programmers that much?
 
Now you're guilty of non-sequitur as well: I was simply pointing out that sum types cover many error handling cases much less cleanly than they are handled now, and was in no way arguing against the use of sum types in general, in Go or otherwise.

The objection makes perfect sense in the context of Go, since consistency, simplicity, and orthogonality are paramount: if you start using sum types to handle some error types, and multi-valued returns to handle others, you violate all three of these key goals.

Don't get me wrong: I don't want to deprive you of anything. Since, unlike many, this is an open-source language, you're free to do anything you want without fear of reprisal.  What's left then is community. I'm asserting that, without the great care, consideration, and restraint that have been present throughout the development of the language, you may (or may not) be surprised at the result. Sure, a great many ideas of merit for improving the language have been rejected, but in general, that has been because those ideas have failed to consider the overall impact, including the negative aspects, that they would cause within the language as a whole.

In any case, every language/project these days goes through a phase like this... "I've looked at your language for 5 minutes, and I can already see what the problem is... it's so obvious!" or "hey, why don't you guys rewrite Linux in C++, since it has feature XYZ?" It's very easy to make such judgements, and it's also very easy to be superficially right, but easiest of all is to make a recommendation while looking from the outside in, knowing that whatever the consequences are, you (the general "you") won't have to live with them.

Regarding the convenience of newbies, it certainly can be a problem. An aside: I don't recall addressing error-handling complacency in the general case, just pragmatism concerning the fmt package specifically. I suspect that a lot of people have forgotten (or never noticed/learned) that fmt.Println even has return values, but it could also be a lot worse, since I really haven't seen production Go code that's ignorant of errors in general. I often find myself having to describe Go as a language of competence: it does a great job enabling those who could have otherwise enabled themselves, yet does not institutional white padding on all the walls to protect newcomers at the expense of efficiency or productivity for experienced programmers.  Go is proving to be quick to learn for first-time programmers, and so we may see concerning trends emerge with respect to the code they write, but from what I've seen, Go still mostly appeals to experienced programmers, which I believe still makes up the majority of long-term community members. I have encountered few Go programmers who weren't already competent in at least one other language.

And no, I don't trust my fellow programmers in the sense that, for security reasons and otherwise, I review any third party code I might think about using. Call me old fashioned if you like.

At any rate, I'll leave the floor open for others to continue with this reasoning as needed. If I stand alone in any or all of these views, it'll soon become evident.
 
+Kevin Gillette , I'll be generous and assume that you did intend to merely say that sum types aren't suitable everywhere. But nobody said that they are, so I am kind of confused about who you were addressing if that was indeed your point. Also, isn't that statement obvious? It's like saying "hash tables aren't suitable for everything, and we already have arrays" in response to the idea of Maps.

> The objection makes perfect sense in the context of Go, since consistency, simplicity, and orthogonality are paramount: if you start using sum types to handle some error types, and multi-valued returns to handle others, you violate all three of these key goals.

(But I thought you were "in no way arguing against the use of sum types in general, in Go or otherwise.") You do realize that sum types would be just another way to compose types, right? What makes you think they're so special? Would you also agree with the statement "if you start using floats to represent some numbers, and integers to represent others, you violate consistency, simplicity, and orthogonality."? If not, what's the difference?

And I really don't know where you were going with those two paragraphs full of platitudes about how open source programming languages evolve. Let's stay focused. Do you or do you not agree that sum types are more appropriate than multiple return values in some error-handling scenarios?
 
In the context of Go, I absolutely do not agree. I hold dear that the appropriateness of any technique in any language depends on the nature of the language, not the quality of the technique.
 
exceptions are nothing more than old school longjmp() without the control.
 
+Vadim Nasardinov I'm yet to see a programming language with automatic error handling. It's not really an error if it's able to be handled automatically by the language.
 
I kind of wish Golang had a syntaxual helper around assign time that allowed me to do something like this:

i, ReturnIf(err) := strconv.ParseInt(str, 10, 64)

That ReturnIf basically should just boilerplate around the "if err != nil { return err }"   (in in named return values case, just call return).

That, or letting go fmt allow this to be one line:
if err != nil { return err }

So the boiler plate at least takes up far less lines. =)
 
+Brady Catherman Yeah, I think a lot of us have wanted that exact same thing at one point or another, but...

For one it only really halves the number of lines, and does even less in reducing the amount of programmer work (if you program with gofmt, then you can type the whole `if err != nil { ... }` bit one one line, without spacing, and let gofmt handle the niceties).

It would be handy to have some kind of capability, like you demonstrated above, to push a return value through one or more one-in-one-out functions, but readability goes down: unlike the rest of golang, here the destination variable name is provided as the parameter name, and any previous value of err, if the variable even existed already, would not be "passed in" to ReturnIf... that's really odd if you think about it (the exact opposite of normal go semantics). Also, a callee being able to return out of the caller is also a bit wonky. Something like the following would be more aligned with Go semantics, but is hardly pretty:

  defer flowreturn()
  i, err:flowpanicif(@@):= strconv.ParseInt(str, 10, 64)

Where flowpanicif would be a generic builtin that would panic with a special sentinel value if the argument is a non-zero value,@0 would mean "the first return argument",@@ means "the argument corresponding to this position", and flowreturn would be another builtin that would recover the special sentinel value or nil, but would re-panic if it received anything else.

Again, I don't really like any of my own ideas for improving the Go language (I find, in a good way, it's really hard to "improve" it). But a change really only has a chance to gain traction if it fits in with existing semantics.
 
I'm a python guy working with go a lot lately and I have to say error handling in go is not what's giving me problems. In fact I like the minor inconvenience to deal with errors right away. And yes If you do want to deal with them in one place it's trivial to bubble errors to caller.
Do not change the syntax at all please. The only point I see is that errors should not be super easy to ignore, by simply not checking them but that can be perhaps solved by gofmt or perhaps a Warn flag to compiler or similar.
 
+Kevin Gillette The biggest issue I have is not so much "number of lines" but flow of code. That is really the thing that raise/throw helps with.

In code I am working on now I have this work flow (pseudo code since its not worth copy pasting):
for i := range(list) {
  id, err := i.SendData()
  if err != nil { return err }
  data, err := i.RecieveData(id)
  if err != nil { return err }
  value, err := strconv(data, 10, 16)
  if err != nil { return err }
  if value != 16 { return new_error }
}

Reading that code takes your mind away from the "flow" and shoves error handling at every single layer. It feels like reading a telegram (SEND DATA, STOP. RECEIVE DATA, STOP. CONVERT DATA, STOP.)

What I want is the ability to say "An error can happen here, but don't worry about it as part of the regular flow we can get to that later." I like returning errors because it makes the flow cleaner and easier to not have surprise panics and such, but having to stop and cleanup every, single, step of the way is annoying. Especially since go fmt would make the above nearly a full page of trivial and exactly the same if statements.
 
Many will disagree with my, but... this is a place I use the assignment part of if-else chains.

If need be, I 'var' declare the pertinent variables earlier, just so that I can gain the structured error handling.

var (
  id int
  data string
  err error
)

for _, i := range list {
  if id, err = i.SendData(); err != nil {
    return err
  } else if data, err = i.ReceiveData(); err != nil {
    return err
  } else if value, err := strconv.ParseInt(data, 10, 64); err != nil {
    return err
  } else if value != 16 {
    return errors.New("some error")
  }
  // apparently you don't need value after the parsing, since
  // it's assumed to be '16'
}
// all the set variables will have the last iteration's values

With this, you get a few benefits. Many will say that this is less readable, since you have to read past the [else if] to see what is being assigned. I disagree with that notion, just as I disagree with opponents of gofmt, if there are any.

When enough people use this style, just as with gofmt'd code vs non-gofmt'd code, it makes visual scanning very fast, and so readability is high. Visually, the if/else chain, along with assignment forms and err use in most of them makes me start reading this as an "error handling chain". The unindented lines (the if's) show what's being assigned for later use, and `err != nil` is a subconscious sentinel saying that yes, we are handling the error case in this if block. The `return err` is also a subconscious sentinel that can be noted and quickly scanned past. Because the next important thing is in the next unindented line, it's easy to jump to.

Cases where specialized error handling occurs (checking for a specific kind of error, or checking for the non-error case) are easy to visually spot because the regularity of these chains makes anything unusual "stick out". This also includes places where the error is actually being handled rather than just passed on to the caller.

In the previous "unstructured" form, I have to read the code, consciously, line by line to understand all that it does, and visual scanning has few anchor points. In the structured form, I only have to read the first half of ~1/2 the lines consciously in order to gain the same understanding, and the code is rife with visual anchor points.
 
+Kevin Gillette readability does become a major issue there if your function calls require lots of parameters, or a formatted string, etc. It also breaks if you need to do three calls that can't error before the erroring call right in the middle of the chain. =)
 
Yeah, but... best tool for the best job. error handling chains certainly can be detrimental to readability in cases where they're not pertinent, including uses of really long function calls (I find that visually, the `err != nil` part should be at least 1/5th the maximum 'if' line length in a short chain, in order to be optimally readable. It's less important in long chains (10 or more if's).
 
func somfunc(somearg sometype,....) (result sometype, err error){
  for i := range(list) {
    var (
      id int
      data string
      value int
    )
    if id, err = i.SendData(); err != nil {
      return
    }
    if data, err = i.RecieveData(id); err != nil {
      return
    }
    if value, err = strconv(data, 10, 16); err != nil  {
      return
    }
    if value != 16 { err = new_error; return }
  }
}
And deal with the error in the caller of somefunc.
 
LabView is an odd language, but it's also an interesting point of comparison here. LabView doesn't have exceptions, instead it's designers encourage a particular pattern of passing errors.

There is something rather like a struct that contains a boolean, an integer and a string. The boolean is the error state: error or no error. The integer is an error number and the string a text description. I'll call that "error_s" for error struct.

If LabView were written like C it's functions would be like:
error_s foo (error_s err, int x, int y)
{
  if (err.state = 0) {
   ... Rest of function ...
  }
}

The style encouraged is that each function always takes and error as an input and returns an error.

Code is written like:
err = Set_Temp(err, T, 1);
err = Set_PSU(err, V, 2, addr);
err = Init_Results_File(err, name, path, ext, T, V);

switch (err) {
... error handler...
}

An error can occur in each of those function calls, if that happens then the following functions won't execute. They won't execute because each one is written like foo above.

In my view this gives the best of both world.  Errors can be dealt with locally, or they can be left to percolate up the program.  Errors in the lowest level modules can easily travel all the way to the top.  Error codes allow particular errors to be dealt with locally, but other errors to be left to travel upwards. Boilerplate code is needed at the beginning and end of each function, but not anywhere else. There's no need for language support either, this could be done in C or Go.

It does have its problems though, programmers can ignore the convention or reuse error code in confusing ways. There's no possibility to do the tricks with unwinding that Common Lisp does.
Add a comment...