# condjump: conditional jumps would make Go code less sawtooth-y

Javascript loading....

A new serious error handling proposal appeared for Go from the maintainers: https://github.com/golang/go/issues/71203. It proposes replacing

  r, err := SomeFunction()
  if err != nil {
    return fmt.Errorf("something failed: %v", err)
  }

with

  r := SomeFunction() ? {
    return fmt.Errorf("something failed: %v", err)
  }

And if you don't annotate the error (you have only "return err") then you can write this:

  r := SomeFunction() ?

I don't like it because it discourages error annotation. The annotation is very important as I argued in @/errmsg and @/goerrors. Furthermore the issue title is "reduce error handling boilerplate using ?" but it doesn't reduce the clutter I find annoying. It reduces some horizontal clutter but it doesn't address the vertical clutter. In this post I'll explore this latter aspect.

(This time I decided to complain publicly on the issue tracker: https://github.com/golang/go/issues/71203#issuecomment-2593971693. I think this is the first time I posted there. I always feel weird to bark my weird opinions into online expert forums. I don't even mind if my comment gets ignored or hidden, at least I had the chance to give my perspective. I will sleep better. I'm super glad the Go team allows such openness even if this adds huge amount of noise to their process.)

For me code with lots of error handlers sprinkled remains visually jarring and annoying to read top to bottom because of the sawtooth-y pattern. And the proposal is very specific to error handling.

Here's my semi-serious counterproposal to reduce the boilerplace experienced when reading code: introduce conditional jumps into the language. Make it possible to write "continue if cond", "break if cond", "return if cond, rv1, rv2, ...", "goto if cond". It results in a more a dense code but I find it more straightforward to read.

Here's a hacky demo of what I mean, hit condense to switch back and forth between the two styles:

Select code:





(The ... syntax is from https://github.com/golang/go/issues/21182.)

It is dense but the code isn't visually jarring so it feels easier to read from top to bottom. Overall I argue the benefits overweigh the drawbacks. Furthermore:

A big con that this changes how Go is written. But so would any other error handling proposal but the above is more generic and would make Go more convenient to read in other, non-error contexts too.

Another big con is that this type of order is weird compared to, say, Python where you would write `return ..., fmt.Errorf(...) if err != nil`. But there the condition is on the right and I think that makes readability harder so I think this proposal is easier on the eyes once one gets used to it.

Oh, and if we are making changes to the language then also make main and TestFunctions accept an optional error return value. Then the error handling can be more idiomatic in those functions too and thus together with this proposal those functions would become more straightforward. Both of these changes would be backwards compatible.

# The panic hack

Sidenote: with panic one can emulate a poor man's one-line error returning:

  func CopyFile(src, dst string) (returnError error) {
    // Note that these helpers could be part of a helper package.
    onerr := func(err error, format string, args ...any) {
      if err != nil {
        panic(fmt.Errorf(format, args...))
      }
    }
    defer func() {
      if r := recover(); r != nil {
        if err, ok := r.(error); ok {
          returnError = err
        } else {
          panic(r)
        }
      }
    }()

    // Normal function logic follows.
    r, err := os.Open(src)
    onerr(err, "copy.Open src=%s dst=%s: %v", src, dst, err)
    defer r.Close()

    w, err := os.Create(dst)
    onerr(err, "copy.Create src=%s dst=%s: %v", src, dst, err)

    _, err = io.Copy(w, r)
    onerr(err, "copy.Copy src=%s dst=%s: %v", src, dst, err)

    err = w.Close()
    onerr(err, "copy.Close src=%s dst=%s: %v", src, dst, err)
    return nil
  }

I don't really like this because it's too much of a hack but for a long chain of error returning functions the hack might be worth it.

# Go dev feedback

What do the Go maintainers think about a proposal like this?

It turns out many people have tried proposing variants of this and all of them were rejected. But most of these proposals were in the context of error handling. As explained above, Go error handling is fine as it is. There are others who also like it as it is, see https://github.com/golang/go/issues/32825.

The only problem with error handling is that it is verbose: returning an error needs 3 lines. But this is because conditional jumps need 3 lines. I'm describing a much general issue here, error handling is just a specific instance of the issue. As such the objections of the previous formatting proposals should be revisited from this perspective and make sure break/continue/goto are covered too. Though some of these proposals did include this too.

Here are couple proposals i found:

Here is sample of Go maintainer responses:

https://github.com/golang/go/issues/27135#issuecomment-422889166 (single line if): We decided long ago not to allow this kind of density. If the problem is specifically error handling, then we have other ideas for that (as noted). But the decision that there are no 1-line if statements is done.

https://github.com/golang/go/issues/27794#issuecomment-430404518 (trailing if): There's no obvious reason to only permit the trailing if on return statements; it is generally useful. But then, there is no obvious reason to permit the trailing if at all, since we already support the preceding if. In general we prefer to have fewer ways to express a certain kind of code. This proposal adds another way, and the only argument in favor is to remove a few lines. We need a better reason to add this kind of redundancy to the language. We aren't going to adopt this.

https://github.com/golang/go/issues/32860#issuecomment-509842241 (trailing if): In my opinion `return fmt.Errorf("my error: %v", err) if err != nil` is harder to read, because it buries the important part. When skimming through the code, it looks like a return statement, so you think "wait, the function returns now? What is all this other code after the return?" Then you realize that this is actually a backward if statement. [...] Making this orthogonal would mean that every statement can have an optional condition, which is a poor fit for the language as it exists today. As [...] said above, it is easy to bury important side-effects.

https://github.com/golang/go/issues/33113#issuecomment-511970012 (single line if): As Rob said in his proverbs talk, "Gofmt's style is no one's favorite, yet gofmt is everyone's favorite." It is far more important to have one format than to debate minor points. There is not a compelling reason to change this one. (The original rationale was to keep conditional code clearly separated from the surrounding code; that still seems like it applies, but the bigger point is that gofmt format is basically done.)

https://github.com/golang/go/issues/62434#issuecomment-1709172166 (on condition return err): The idea of making error checks a single line has been proposed several times, such as in #38151, and always declined. This proposal is slightly different in that it omits the curly braces, but it's not very different. This would also be the only place where a block is permitted but optional. Also the emoji voting is not in favor. Therefore, this is a likely decline. Leaving open for three weeks for final comments.

So yeah, the maintainers are not too keen on this. In fact the emoji voting in https://github.com/golang/go/issues/62434 suggests that most people don't like proposals like this, it's not just the maintainers.

Though if people keep opening issues like this then there's clearly a demand for such a simple solution to the boilerplate. I wish the Go team would ask in a dev survey if people's problem is about the horizontal or the vertical boilerplate. Most error handling proposals seem to be optimizing the horizontal boilerplate but it might well be that people's problem is the vertical one.

# My actual opinion

To be fair, I'm a bit torn on the issue myself: I don't want Go to evolve further. And I don't like that it would allow expressing the same logic in two different ways. People would just argue which one to use and when. While I would welcome this but I think at this point it's too late to introduce such a change into the language. The main point of this post was just the above demo to serve as a comparison, not actually proposing such a change.

At most I might raise a point in the next dev survey that Go team should perhaps consider this approach as a less visually jarring error handling but I don't expect much from it. I also hope https://github.com/golang/go/issues/71203 doesn't pass. Fortunately the emoji feedback is quite negative at the time of writing.

published on 2025-01-15


posting a comment requires javascript.

to the frontpage