Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: Go 2: allow goto to jump across variable declarations #27165

Closed
griesemer opened this issue Aug 23, 2018 · 11 comments
Closed

proposal: Go 2: allow goto to jump across variable declarations #27165

griesemer opened this issue Aug 23, 2018 · 11 comments
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@griesemer
Copy link
Contributor

griesemer commented Aug 23, 2018

Problem statement

The current rules for the goto statement state:

Executing the goto statement must not cause any variables to come into scope that were not already in scope at the point of the goto.

In short, a goto statement is not allowed to jump across variable declarations (jumping across nested scopes that declare variables is ok). The underlying reason for that rule is that such variable declarations will not be executed when the goto is taken and thus the respective variables may be in an undefined state (not initialized, not heap-allocated, etc.) at the target label.

This is unfortunate because many times when a goto is used at all, we don't actually care about the values of such variables. A perhaps typical example is the use of goto to factor out error handling to the end of a function:

func f() {
    ...
    if !cond1 {
        goto Error
    }
    if !cond2 {
        goto Error
    }
    ... code here cannot declare new variables even if they are only used locally ...
    if !cond3 {
        goto Error
    }
    ...
    return

Error:
    // handle error
}

Other common scenarios are uses of goto's for early exit where there is no error handling but some finishing up code (e.g., look for goto done, goto out, goto skipped, etc. in the std library).

If the code above needs to use a new variable in the code following the goto and before the Error label, that variable cannot conveniently be introduced close to where it is used, but instead will need to be declared before the goto. Often this also means that one cannot use a short variable declaration because the program state is not what is needs to be for an initialization expression.

In summary, using a goto sometimes requires that otherwise unaffected code will need to move variable declarations before the use of the goto since the goto would not be permitted otherwise. This is at best annoying, and at worst may lead to less readable and maintainable code due to bulk variable declarations, without initialization expressions, sometimes much before they are actually needed.

Observation

Obviously, if code accesses a variable at a target label of a goto, that variable must be declared before the goto as otherwise its value may be unknown. But the values of variables that are not accessed at the target label don't matter exactly because the code doesn't use those values.

This observation leads directly to the proposal.

Proposal

We propose to remove the goto restrictions on variable declarations in favor of a new rule for variable accesses (edited per discussion):

A variable that would normally (without the presence of any goto) be in scope at a label declaration is not in scope if there is a goto to that label which jumps over the variable's declaration.

It is an error to access a variable (lexically) after a label declaration if there is a goto to that label which jumps over the variable's declaration.

Examples

The code fragment:

func _() {
	goto L
	x := initexpr
	use(x)
L:
}

currently is invalid and leads to the compiler error:

x.go:4:7: goto L jumps over declaration of x at x.go:5:6

To make it valid, we would need to write something like:

func _() {
	var x T // declare x here because of goto below
	goto L
	x = initexpr
	use(x)
L:
}

With the proposed new rule, the goto error will disappear, making the original code fragment valid. On the other hand, if the code were to make use of x at the label L as well:

func _() {
	goto L
	x := initexpr
	use(x)
L:
	use(x)
}

the compiler might report instead (edited per discussion):

x.go:4:6: cannot access x (goto L jumps over declaration of x at x.go:5:6)

The error reported currently with the goto statement could be used literally as explanation in the error reporting the invalid variable access at the target label.

It is important that the variable remains in scope to make it impossible that another variable with the same name is declared in what used to be the variable's scope if there was no goto. For instance, the following code remains invalid with this proposal:

func _() {
	goto L
	var x T
	use(x)
L:
	// per the proposal, x is inaccessible here, yet it remains in scope
	var x T // <<< ERROR cannot declare another x in the same scope
}

Implementation

The implementation is expected to be straight-forward: The new rule shifts an existing error, reported with a goto, to a specific access of the variable which caused the goto error. More detailed:

Whenever the compiler's analysis detects that a goto jumps over a variable declaration, instead of reporting that as an error, the compiler remembers that variable as inaccessible at the target label. At the target label, all such inaccessible variables are removed from scope. This will in turn make it impossible to access such a variable in the code. (A more user-friendly implementation might keep the variable in scope but upon access would also check if it was marked inaccessible. Such an approach could provide a better error message). If the variable is accessed lexically after the target label, the compiler reports an error.

Alternatives

#26058 proposes an alternative approach to achieve a similar outcome: Instead of disallowing variable access if a goto jumped over the variable's declaration, #26058 disallows a goto if it jumps over a variable declaration of a variable that is used after the target label. In other words, #26058 makes the existing restriction on goto's slightly less onerous while this proposal removes that restriction altogether but introduces a new one for variables.

Summary

This proposal removes a restriction on uses of goto's in favor of a new rule for variables: Instead of making it invalid for a goto to jump over a variable declaration, it will become invalid to access a variable (after a label) whose declaration was jumped over. This is an improvement over the existing situation because the current rules always disallow variable declarations that are being jumped over, while the new rules only lead to an error if such variables are used at the target label, which is a less common scenario.

Because the proposal removes an existing restriction, this is a backward-compatible language change.

#26058 achieves a similar outcome through a slightly different approach. It does not seem obviously clear which approach is better.

@gopherbot gopherbot added this to the Proposal milestone Aug 23, 2018
@ianlancetaylor ianlancetaylor changed the title Proposal: Allow variables to jump across variable declarations (in many circumstances) proposal: Go 2: allow variables to jump across variable declarations (in many circumstances) Aug 23, 2018
@ianlancetaylor ianlancetaylor added LanguageChange v2 A language change or incompatible library change labels Aug 23, 2018
@robpike
Copy link
Contributor

robpike commented Aug 23, 2018

I like this.

@magical
Copy link
Contributor

magical commented Aug 23, 2018

@griesemer Should the title say "allow goto" instead of "allow variables"?

@magical
Copy link
Contributor

magical commented Aug 23, 2018

I

Would it be valid to redeclare a variable after a goto? For example,

func _() {
	goto L
	var x int
L:
	var x int
}

That is, are the lines between goto L and L: a new scope, or some sort of pseudo-scope? In other words, is the above code equivalent to

func _() {
	goto L
	{
		var x int
	}
L:
	var x int
}

II

Does this interact with garbage collection at all? If i understand things correctly, currently the garbage collector is allowed to reclaim variables as soon as they are no longer used, regardless of whether they are officially "out of scope", so nothing would really change under this proposal. Just checking.

@griesemer griesemer changed the title proposal: Go 2: allow variables to jump across variable declarations (in many circumstances) proposal: Go 2: allow goto to jump across variable declarations (in many circumstances) Aug 23, 2018
@griesemer
Copy link
Contributor Author

griesemer commented Aug 23, 2018

@magical Thanks for catching the typo in the title. Fixed.

No, I think it would be a mistake to introduce what you call "pseudo scopes". That would make this proposal much more complicated. It would also lead to hard to understand code, because one could have what looks like double declarations of variables (per your example) which are in fact different variables even though they appear textually in the same scope. That seems like a bad idea.

This proposal is strictly about restricting the scope access of variables whose declarations are being jumped over by gotos. Anything beyond that will make this more complicated and likely is not worthwhile the trouble.

It's important to keep in mind that using labels and goto's is not really a recommended coding style. But in some limited situations, using a goto is exactly the right thing. And in those cases it would be nice if we wouldn't have to compromise the quality of the code further by being forced to move variable declarations from the preferred locations to before a goto. This is what this proposal is all about. A nice side benefit is that the change is backward-compatible, simple to implement (I believe), and easy to explain in the spec.

Regarding garbage collection: The question is not so much about garbage collection, but about the layout of variables in an activation frame. As long as we make sure that variable locations are properly initialized at all times for garbage collection to work, or the compiler can provide the correct used/unused information at each GC point, I think there shouldn't be a problem here. For all I know there is nothing to do here, but @randall77 or @aclements will be able to give the authoritative answer.

@magical
Copy link
Contributor

magical commented Aug 23, 2018

@griesemer Thanks for the clarification. I agree that it would be overly confusing, so I'm glad that it wouldn't be allowed.

@FMNSSun
Copy link

FMNSSun commented Aug 23, 2018

I like this but you don't have to declare the variable before the goto if you use

func main() {
	cond1 := false
	cond2 := true
	cond3 := false
	
	if !cond1 {
		goto exit
	}
	
	if !cond2 {
		goto exit
	}
	
	{
		i := 5
		if i == 5 && !cond3 {
			goto exit
		}
	}
	
exit:
	fmt.Println("exit")
}

@aclements
Copy link
Member

For all I know there is nothing to do here, but @randall77 or @aclements will be able to give the authoritative answer.

I'm not 100% positive, but I believe you're right that this doesn't have any particular interaction with the garbage collector. Variables affected by this proposal must already be dead at the label, so it won't matter whether or not they're initialized in the frame.

It's possible this would have some interaction with ambiguously live variables, for example if you jump from a location before an ambiguously live variable is declared to a location where it is ambiguously live. This may require zeroing the ambiguously live slot before the jump. I suspect @randall77's stack tracing change will make this a moot point anyway.

@griesemer
Copy link
Contributor Author

griesemer commented Aug 23, 2018

I've edited the proposal per the discussion in #26058 (comment) .

Also, the original proposal text suggested that variables that must not be accessed after a label should be removed from scope to achieve that effect. But that would permit another variable with the same name to be declared in the original scope. That seems like a bad idea. In #27165 (comment) I had explained as much without noticing the difference from the earlier proposal text. I've added a further example to the original proposal text.

@griesemer griesemer changed the title proposal: Go 2: allow goto to jump across variable declarations (in many circumstances) proposal: Go 2: allow goto to jump across variable declarations Aug 23, 2018
@griesemer
Copy link
Contributor Author

Possible refinement: Instead of disallowing variable access after a label if a goto jumped over the variable's declaration, it may be sufficient to disallow variable "reads". (A similar refinement may apply to #26058).

@sdwarwick
Copy link

Wrapping code between the goto and label with a {} block seems to work now and provides some explicit indication of intent regarding scope of declared variables in that range. Putting gotos inside the block to jump out also seems to work now.

Just worried about impacts to one of golang's greatest features.. clarity of intent is very explicit.

@ianlancetaylor
Copy link
Contributor

Closing in favor of #26058, which is the same idea expressed in a slightly different way. Both issues accept the same set of programs; the difference is whether the error is reported on the use of the variable or on the goto statement. The (small) advantage of #26058 is that it does not change variable scope. It just relaxes, slightly, the existing constraint on goto over a variable declaration.

@golang golang locked and limited conversation to collaborators Oct 2, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

8 participants