go: proposal: Go 2: permit goto over declaration if variable is not used after goto label
Right now you get this error: goto SKIP jumps over declaration of queryArgs
if len(s.standardDays) == 0 {
goto SKIP
}
queryArgs := []interface{}{}
//Do stuff with queryArgs
SKIP:
//Do stuff here BUT queryArgs is not used ANYWHERE from SKIP onwards
Currently I have to refactor all my code like this (including placing var err error at the top):
var queryArgs []interface{}
if len(s.standardDays) == 0 {
goto SKIP
}
queryArgs = []interface{}{}
//Do stuff with queryArgs
SKIP:
//Do stuff here BUT queryArgs is not used ANYWHERE from SKIP onwards
About this issue
- Original URL
- State: open
- Created 6 years ago
- Reactions: 15
- Comments: 28 (24 by maintainers)
Instead of moving variable declarations to the top, you could just introduce a new block around what you’re goto-ing over:
https://play.golang.org/p/kzFxyaeOmkQ
FWIW, I hit this issue today when deleting a bunch of code:
https://go-review.googlesource.com/c/go/+/199499/4/src/cmd/link/internal/ld/elf.go
Instead of just deleting the if statement and unindenting, I had to leave the otherwise unnecessary braces in because there was a goto jumping over this code.
The status quo enables optimizations that would be no more [easily] possible under the proposal.
Having spent a fair bit of time porting old numerical Fortran code to Go, I’d seen a lot of
GOTOuse and abuse. Of the problems that come from use ofgotos, this is by far and away the least problematic, the more significant issues have been (at least in those code bases) non-nesting of loops (or at least nesting of loops that are not trivially nestable) and identifying labels. Hoisting has always been a relatively easy process to work around the restriction here (and the restriction is a good motivation to remove as manygotostatements as possible).Anecdotally, this restriction is extremely frustrating when working by hand on non-trivial functions involving gotos. My experience is with trying to clean up and clarify the compiler after the c2go rewrite. Being able to shorten variable lifetime is an important step in making code clear. I often found that I had to refactor to remove gotos, even in cases in which they did not otherwise impede readability, before I could do any other meaningful cleanup.
The current goto rules balance two competing goals: (1) don’t allow valid Go programs refer to uninitialized variables, and (2) don’t require complex analysis to decide whether a Go program is valid.
@pjebs’s proposal and @mark-rushakoff’s rewrite are different in general, although perhaps not in this case, and the difference highlights exactly how we have balanced these two competing goals.
It is true that this invalid Go program would, if it were redefined to be valid, not refer to uninitialized variables:
It is also true that it can be mechanically transformed into this valid Go program:
(The only change is the introduction of
{and}on two new lines.) However, here is an invalid Go program that does refer to uninitialized variables:The mechanical block rewrite preserves invalidity, because the
goto L2cannot jump into a block:(Again, the only change is the introduction of
{and}on two new lines.)It is of course good that adding braces has not taken this bad program from invalid to valid. That is why the block restriction exists.
The original message on this proposal did not suggest a detailed fix (the original title was simply “fix goto”). It has been retitled “permit goto over declaration if variable is not used after goto label” but a literal reading of “after” meaning “after in the source code” would violate goal (1) by admitting this buggy program. A reading of “after” meaning “after in the control flow” would violate goal (2) by requiring more complex compiler analysis to decide whether a program is valid.
Obviously, C compilers do this kind of analysis in their “used and not set” warnings, but experience has shown that these are sensitive to how aggressively the compiler optimizes (= learns new details about) the code. Whether a particular input is a valid Go program must not depend on compiler optimization level. There needs to be a clear, simple process for deciding. That process must produce consistent results in all implementations, and it must not admit any buggy programs.
As an example of where we have solved this kind of problem in the past, functions with results originally had to end in a
returnstatement, but that was problematic for things likeWe fixed this by writing a precise definition of a terminating statement and requiring that functions end with one of those instead of just a return.
We could conceivably do something equally precise here. But I think the definition would end up being longer, since it would have to capture some clear algorithm for deciding control flow. The current rules avoid introducing a control flow algorithm into the spec, because of goal (2).
I do see one possible simple change, which would be to say that it’s ok to jump over declarations (unconditionally), and that jumping over one is the same as executing it, meaning the name is in scope and has the zero value. Goal (1) is satisfied because the programs never see uninitialized variables, and goal (2) is satisfied because there is no complex logic about whether a program is valid. An optimizing compiler would need to initialize the value earlier than it comes into scope, by sliding any (zeroing) initialization code backward up to just before the first goto that might jump over the value. This is basically what @pjebs says he does by hand already in the top comment.
Is this worth doing? Maybe, maybe not. That’s an open question.
Notwithstanding @josharian experience, from my experience, it has been a minor annoyance, but nothing too monumental to overcome.
The proposal provides benefits that are intangible:
gotowill behave as it intuitively should. This is good for beginners of Go. I feel it makes the language more internally consistent too but that’s prob a Rob Pike Q.To clarify, it hasn’t been declined, either. It’s pending a decision.