go: proposal: go/printer: don't reformat single line if statements
This proposal is mainly due to @bradfitz and likely others. It’s similar to #33113 but we could actually choose to adopt it.
I’m not sure that this is a good idea
The proposal is to not format certain if statements if they are written as a single line. This will permit more concise code for certain common error handling patterns.
Consider these two functions. They are the same sequence of tokens, formatted differently. However, gofmt today will not change either of them.
func F1() {
go func() { fmt.Println("hi") }()
}
func F2() {
go func() {
fmt.Println("hi")
}()
}
This is because go/printer permits a go statement with a simple function literal that is written as a single line to remain on a single line.
We could apply a similar rule to if statements. Basically, if
- an
ifstatement is written on a single line, and - the body of the
ifstatement is a singlereturnstatement, and - nothing in the
returnstatement would normally be formatted on multiple lines (such as a composite literal), and - there is no
elseclause
then go/printer would not modify the if statement: it would remain on a single line.
Here is what the example from https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md might look like if this were permitted:
func CopyFile(src, dst string) error {
r, err := os.Open(src)
if err != nil { return err }
defer r.Close()
w, err := os.Create(dst)
if err != nil { return err }
defer w.Close()
if _, err := io.Copy(w, r); err != nil { return err }
if err := w.Close(); err != nil { return err }
}
In other words, this proposal would permit more concise error handling without changing the language. It would be concise not in the sense that it would use fewer tokens, or that it would require less typing, because it wouldn’t, but in the sense that it would be less obtrusive on the page.
This proposal would not affect any existing code. It would merely permit people to use a different formatting style for this specific case, and gofmt would preserve that formatting style.
Of course, that is also a disadvantage, in that it would introduce a variation in Go formatting, one that is more significant in practice than the formatting of go and defer statements. One could imagine arguments about the formatting style to use, which would go against the goal of gofmt.
As I said above, I’m not sure this is a good idea, but I wanted to write it down for discussion purposes.
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 41
- Comments: 36 (15 by maintainers)
I am of the opinion that control flow should not be buried in the middle of the line, so the
returnkeyword should be the first token on the line.There are exceptions, the most common in my own code being the one-line functions satisfying
sort.Interface. But I am unsure whether error handling, although a common pattern, should follow that lead by hiding itself mid-line. There are details in a handled error that should not be shrouded.I wouldn’t consider the omission of two newlines to be considerably easier.
Which I think is an argument against this, not for it. The lack of indentation conceals control flow.
It is opt-in for the author, but not for the reader.
@beoran Your response captures my objection. Yes, it’s good for the non-error path to be nice to read, but it’s also important that the error path be visible.
if there is a problem in error handling in Go, it’s not the readability, it’s the belief that all one writes is
That belief has led to a lot of bad code and bad thinking. The error path should actually handle the errors, not just blow them off.
As I wrote, the details of error handling should not be hidden. But this proposal not only obscures them, it encourages the
return errapproach to handling errors by making it easier to write bad error handling that just passes the buck. Keeping the handling clearly separated makes it easier to see when the error handling is insufficient.It is weird to read that, considering that one can always write the following code:
and be completely fine about having that large struct being produced in one line (at least gofmt is is fine), but we consider the following:
hard to read. I know, it is subjective, but consider these accepted gofmt style examples:
It’s matter of style using each one of those. That’s quite liberal, and in my opinion that’s good, gofmt tries to adapt to most programming styles. Why then should the general style be conservative with adapting
if err != nil { return err }in one line?To add to the discussion, I don’t think I would ever write the
CopyFileas currently written, but rather:So it is easier to debug when using delve.
Though it was discussed before in the context of “try”, we didn’t discuss it as a standalone proposal yet. I think now as I thought then that this proposal is the easiest way to simplify error handling in Go.
This proposal makes it easier both for the writer to write a single error return, and for the reader to skim over the error handling lines, and has many other benefits as described in the discussion linked above. Also, since this is essentially opt in, if will have no effect on existing code. The only downside I can see is that it might lead to inconsistency and discussions about what style to use, but for new projects I would definitely use the single line style for all if err != nil { return err } statements.
There’s definitely a real problem here with how to reduce the pain and verbosity of error handling without encouraging bad error handling. It’s unclear that single-line if statements does that. I still think we should move toward something like ‘try’ if we can figure out how to make it work (we got hung up on interaction with defer and other control flow issues last time).
I wrote this code earlier this week:
This is not what I’d recommend in general but it did reduce the error handling quite a lot.
@robpike yes, I agree this proposal makes the return err somewhat easier, but I don’t think it is necessarily a bad thing.
In large, multi layered software, the middle level functions often do not have enough information to truly handle the error. The best they can do is decorate or wrap the error with return fmt.Errorf(“decorations: %w”, err), but in many cases, the low level error is good enough and can be passed on to the higher level functions which decide what to do and truly handle the error. If we look at existing go code the return err pattern, is extremely common for such reasons. Some “bucks” really need to be passed.
Mmm, I believe for people that care a lot about having an extremely consistent looking codebase style wise there’s
gofumpt, the strictergofmt. That tool could also format allif err != nilstatements into 3 lines.At the end, it is up to the author whether they make an error handling block 3 lines or one. For example:
I think most would agree to write the second form.
gofmtis even more flexible, it allows you to do this:and there are plenty of more cases where gofmt is flexible about how many lines an expression can take:
Wouldn’t you agree aswell that if the community was to be extremely splitted that easily, it could happen with how functions are defined, for example?
The proposal allows two formatting styles for typical error handling code. The pattern
is part of every code base I have seen. The proposal will split the Go community in two camps. It has the potential to become the tabs vs spaces issue for the Go community. If there are benefits in compacting those statements, it should become mandatory.
However I don’t think the change would be beneficial, because even this proposal has the weakness, that all proposals about error handling have; they want to make error handling disappear on various levels. I always thought, that making code flow – particularly conditional code – visually explicit, was a strength of the Go code formatting style. I don’t want to lose this strength.
My big concern with this proposal is that the
if err != nil { return <something derived from err> }pattern is so fundamental to practical Go programs that it’s very likely that any given Go program will include examples of every possible style thatgofmtallows.Other situations I can think of where
gofmtis unopinionated are situations where I expect there are far fewer examples of them in a typical program, although of course that’s just a hunch and I don’t have any data to back it.Because the error handling pattern is something that every Go developer will need to learn and recognize, I think it’s particularly important for it to have a very similar shape in every Go program, so that our pattern-matching brains can quickly recognize it when scanning code.
If
gofmthad always formatted this kind of statement in this way, or if it were feasible to retroactively changegofmtto force this new style on all existing programs (which I don’t think is true) then I would be fine with it, but I’m specifically concerned about there being two allowable ways to present this pattern, and so that is why I voted 👎 .@bradfitz Most error handling proposals have the implicit or explicit goal to reduce error handling to a single line, in order to improve the readability of the “happy path”. As long as that important point is not addressed, new proposals will keep on being filed. Now we have generics, this point is one of the points blocking the wider acceptance of Go. This proposal is a small concession towards that point, but it will be be sufficient in many cases, without much negative side effects.
@beoran I never type them, either, because I rely on gofmt to insert them for me.
@shasderias The fact that they went through the effort of implementing such code folding shows that there are enough Go developers out there who prefer to read the single line style.
@dominikh I never type those two newlines in an if err != nil { return err } , on an average Go program this does save me some typing. I wish go fmt wouldn’t insert them either.
I see here and in many other error related issues that many Go code readers want to skim the simpler error handling lines to be able to understand more easily what the “happy path” through the code is doing, and I agree with that
For parts of the code where errors need to be handled in detail, this proposal will not apply anyway, since more thorough error handling will not fit on a single line.
As @not-rusty said, for other code constructs, go fmt now allows us to choose which style we prefer, and that works great. I think that for simple if err != nil { return err } statements, there is very little harm and many benefits to allowing the developers to choose likewise.
FWIW, there was a decent amount of discussion about allowing certain if statements to format on one line as part of the
trydiscussion in #32437. If I recall correctly, it might have been @zeebo who first raised it there.Here’s a link to that group of comments:
https://swtch.com/try.html#gofmt1
(This proposal might be a bit different than what was previously discussed, including I don’t recall whether “merely permit people to use a different formatting style” was discussed, but just trying to link to related discussion, including @zeebo and perhaps others provided some additional concrete examples there).
I don’t think this proposal will be that divisive, when looking at @not-rusty’s examples, which also did not split the community.
Actually I would say that the benefit of this proposal compared to most other error handling proposals is that it does not hide error handling, but just makes it easier to read the “happy path”.
@beoran That’s true, but besides the point. I think the fundamental question here is: gofmt respects the writer’s intent of where to place line breaks in certain circumstances and disregards it in others. Does a single line if statement fall under the former or the latter?
Taking one step back, editors alter and augment the presentation of source code to assist the programmer. Given that an editor can present a single line if statement in its compact form much more effectively than gofmt can - IntelliJ elides braces and the return keyword when folding a single line if statement, is it still necessary to place this responsibility on the language?