A small proposal regarding error handling in Go

I’ve mentioned on reddit a few times, very succinctely, an idea I’ve had regarding a small syntaxic sugar aimed at easing the handling of errors. As it gained some (small) attention I think a more readable presentation is needed.

The usage case is the one where typically Go ends to be more verbose that exception based languages while we really have nothing to do with the error at that level. It happens. All Go coders have been a little annoyed by this kind of situation :

func Things(path string) ([]int, error) {
	srcdir, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer srcdir.Close()
	filenames, err := srcdir.Readdirnames(-1)
	if err != nil {
		return nil, err
	}

	...
}

The solution I propose is similar to the one we use to dismiss an unneeded parameter (that is using the _ symbol). I propose to use another symbol to return an error when the value is not nil. I thought about the ^ symbol because it seem to convey the idea to rethrow to the caller but the choice of the symbol is, for now, a detail.

func Things(path string) ([]int, error) {
	srcdir, ^ := os.Open(path)
	defer srcdir.Close()
	filenames, ^ := srcdir.Readdirnames(-1)

	...
}

The code would be more consise, more readable and the logical flow would be preserved. The standard and generally preferable in place error handling and the existing code base would be left untouched.

More precisely:

  • there would be a compilation error if the ^ symbol is used and the function doesn’t have exactly one returned parameter of a type implementing the error interface.
  • at the occurrence of the non null error being returned, all unamed return parameters would be left uninitialized and all named return parameters would keep their value at the moment of the error

Note that this idea could be extended to other types than errors but I’m not proposing that now as I’m seeing only marginally useful use cases and more complex rules arising.

5 réflexions au sujet de « A small proposal regarding error handling in Go »

  1. This kind of thing has been suggested before on the Go mailing list. The main issue is that it makes error magic, which is generally seen as a bad design. It’s just a type. Anything you design should be orthogonal to types.

    One design is a function called « must », with the signature `func must(T…, error) T…`, where T is a list of types. It panics if the error is not nil, and otherwise returns the other arguments. This approach is used in the text/template package, but there’s no way to write the function generically.

    Once you’ve panicked on the error, you can defer a function to recover from it and assign the error to the return value of your choice.

    Since `must` isn’t writable in general, sometimes when people are taking this shortcut, they use a check function and just pass the error in after every function call.

    Either way, this kind of approach is only really good in small limited cases since it hides the error handling path, which may seem convenient in some cases, but is bad in general. As soon as people get in the habit of just passing errors up without having to write the code to do it, they aren’t thinking « should I be handling this now? ». Quite often, then answer to that is « yes », even if its just to add context to the error.

    • Firstly, there are a lot of people today (not me) who say that the biggest fail of Go was to forget exceptions. Why ? Because errors, that is what makes your program leave the normal path, the one on which you built your function (as in « what it is meant to do »), are important. Saying there are just a standard type seems to me some kind of dogma.

      Secondly, cutting expressiveness because it can be abused is wrong. Of course it would be abused but the role of the language isn’t to try and make bad developers produce good code : it’s to help good developers make better code.

      • I understand that you are sensitive because you are protecting your idea, but it shouldn’t get in the way of considering what people are saying. Pointing out that « error » is a type in Go is not « dogma », it’s simply a fact. Whether it *should be* is a matter of opinion, but calling any opinion you don’t hold « dogma » is extremely arrogant and not conducive to constructive discussion. Your behaviour in this regard is enough for anyone to legitimately disregard what you’re saying.

        I’ll point out that errors are part of what your code is supposed to do. Errors will happen, and handling them is part of the normal operation of a program. I don’t think anyone can really dispute this. Where people disagree is on whether this handling should happen in your code, or be sequestered off somewhere where you don’t have to think about it if you don’t want to.

        Expressiveness should never be added for its own sake. It should have a practical benefit that outweighs the cost, and there is always a cost. Certainly, you sometimes have to give people enough rope to hang themselves, but there’s no point giving them enough to hang themselves but not enough to do much else. It seems, in this case, that the potential for correct use is fairly limited and of minimal impact, while the potential for misuse is enormous, and looking at other languages you can see that this kind of feature is widely misused.

        Since we’re discussing this, what do you thing of the idea of having a generic « must » function? In my opinion, it would fit in Go better. However, I doubt the authors would add it, because they want to discourage people from ignoring errors in code, even though they recognize that sometimes that’s what you need to do (evidenced by its inclusion in a standard package).

        • You’re perfectly right that the use of the word dogma in my too fast typed comment wasn’t kind nor helping the discussion. And I’m sorry about that.

          What I was trying to say with my bad English is that that the fact that errors were initially coded using a completely general and unspecial type shouldn’t prevent us to see that they have the special meaning of the thing breaking the normal flow of the algorithm. It’s elegant to use a standard and general construct for errors but my opinion is that errors (or exceptions) are, in fact, important and different. And I sometimes have the feeling that Go supporters, being fed up (with reasons) with the attacks about the lack of exceptions in Go, are kind of a little self-blinded about what I see as a fact. There you may understand why I used the word dogma. Where I’m aware I may be wrong is about the opinion that there are important enough to justify such a construct.

          And, maybe I’m wrong too about the relative importance of good use cases versus misuse as I feel it depends a little of what you code. When your code use IO for example it’s almost the general case to have to let the caller handle the errors as they’re external to the callee logic.

          I won’t comment about the « must » function for now, as I haven’t yet any well thought opinion and it’s almost 3 AM here…

          • J’ai remarqué que la site soit en français, mais pour quelque raison je n’ai pas penser au possibilité que vous pourriez être francophone, ni que vous soyez peut-être dans un différent fuseau d’horaire. Ceci indique que votre anglais est beaucoup meilleur que mon français ;) . Though I may have guessed wrong on the language…

            There is a degree to which Go users are tired of people saying that Go *needs* exceptions, anyone will probably admit that. There have been many discussions about how to make this « just pass it up » case easier, some working more or less like yours, some more esoteric, some more pragmatic. But what it keeps coming back to is « what does this feature give us? » and that’s a more complicated spec, and slightly tidier code in some places with little other benefit, at the expense of making some fairly important code paths non-evident.

            I find it interesting that you picked IO, because that’s one of the main places you almost *always* need to handle at least some subset of the errors, even if it’s just checking for EOF (which maybe shouldn’t be indicated by an error anyways but its a bit too late for that), so your proposal wouldn’t apply there.