-
Notifications
You must be signed in to change notification settings - Fork 18k
spec: when exactly do we evaluate the LHS variable in a tuple assignment? #15620
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
Comments
It's not obvious to me that this program is well-defined according to Go's order of evaluation rules. The rules say that in a tuple assignment all index expressions are evaluated on both the left and right hand sides. Then all the assignments are done in left to right order. What the rules do not say is precisely when the first It is at least possible that the precise behavior of this program is not defined by the spec, and that gccgo is implementing it correctly. CC @griesemer for a second opinion. |
Per Order of Evaluations (https://tip.golang.org/ref/spec#Order_of_evaluation), functions calls are executed from left to right, and per the example in that section, the
There are no assignments to
Per the spec "First, the operands of index expressions and pointer indirections (including implicit pointer indirections in selectors) on the left and the expressions on the right are all evaluated in the usual order. Second, the assignments are carried out in left-to-right order." (https://tip.golang.org/ref/spec#Assignments). The operand of the index expression on the left is 4 (and it would still be 4 even if we evaluated it only now). Then all the expressions on the right are evaluated. The result of the
So we are left with:
Note that the spec says that the operands of index expressions are evaluated first, not the index expressions themselves. Then assignment occurs from left to right. That is, only then are the index expressions evaluated, from left to right as the assignments occur:
After the first assignment, the length of I'd argue that this is a bug in gc and gccgo is correct. I'd also recommend to not write code like this (correct or not) as it is not easily readable in the first place. |
I think your analysis relies on a rather close reading of the assignments occurring from left to right. In this example, does that really imply an exact order for when we read the variable In any case I'm glad you agree that gccgo is not wrong. |
I removed the questionable example from the wiki page. |
@ianlancetaylor I think a close reading will be required to determine corner-cases such as this one. I don't remember the details of the original discussions, but the spec is careful in distinguishing between evaluation of operands (of index and indirection expressions), and evaluation of entire expressions. To contrast, if the expressions
is of similar nature: The assignment of PS: This is of course rubbish. The panic is happening during the |
I think the spec is clear. We may want to add an explicit sentence and perhaps add a couple of corner-case examples at best. Not urgent. |
@griesemer In your last example, the run-time panic is due to the dereference of p in For what it's worth, my reading of the Go spec is that
should be equivalent to:
Which supports cmd/compile's current behavior: https://play.golang.org/p/bFofYVAqt- |
@mdempsky You are correct about the *p panic. Hm, the indexed expression is also an operand, point taken. It's probably worthwhile highlighting the subtleties with some specific examples in the spec. |
Is the following description for value evaluation orders in assignments better? Any assignment statement can be rewritten as the following form finally,
Where each Lx is a scalar address value, and each Rx is a scalar value. For example (assume a is a slice which element type is int),
will be rewritten in the following steps:
If a map element (which is unaddressable) appears at left side, For example (assume m is map and p is a pointer, the map element type
will be rewirtten in the following steps:
The extra steps may be not the last steps. For example
will be rewritten in the following steps:
Here, |
Sorry, I'm not sure I understand what you mean by better here. However, I'll point out that I think the rules you described are incomplete, and also stricter than the current spec. If you're suggesting the spec should take this approach, I'm inclined to disagree. First, your description of how to handle map assignments doesn't handle when to evaluate the map or its index. For example,
should not panic. Your description seems to suggest the assignment will be rewritten as:
However, this will panic, because the Second, the spec currently only partially orders evaluation of subexpressions, and a rewrite ordering like this will impose additional ordering constraints. For example, you suggested the first rewrite for
which suggests the |
Yes, I admit it has some defects. I think its biggest defect is it treats array/slice and map differently. So here I try to make a revision for it.
[update]: for array and slice element destinations, they can also be represented as *Px. |
To make it more consistent with the following description in Go spec:
a new revision is made. ================== Just after the preparation phase and before the carry-out phase of an assignment, the assignment is rewritten as the following elementary form:
where each Lx may be
and, every |
edited the last comment. |
I'm still unclear what your goal is with this alternative description of order of evaluation rules. Are you suggesting they could be incorporated into the Go spec? If so, the Go spec needs to describe the exact rules that programmers and compilers must operate under. An explanation that doesn't cover all admissible implementations isn't very useful. If the language spec allows implementations to not obey it, then how would users know what language behavior to expect? |
That updated line is mainly for the first revision. The last revision is the most satisfied one currently. These revision attempts are trying to make the explanations for the evaluations of all left items uniform.
The current description in Go spec is not very helpful to explain the mechanism of exchanging values of two variables: a, b = b, a With my second revision, it can be explained as the following steps: // The preparation phase:
P0 := &a; P1 := &b
R0 := a; R1 := b
// The elementary form: *P0, *P1 = R0, R1
// The carry-out phase:
*P0 = R0
*P1 = R1 And the second revision is good to clear the disputes in this issue thread: #23188 // arr, arr[len(arr)-1] = arr[:len(arr)-1], 3
// The preparation phase:
P0 := &arr; P1 := &arr[len(arr)-1]
R0 := arr[:len(arr)-1; R1 := 3
// The elementary form: *P0, *P1 = R0, R1
// The carry-out phase:
*P0 = R0
*P1 = R1
kind of, but the current description of the second revision is no ways perfect now, |
In the current spec, the swap
|
Yes, this is the cause for the dispute in #23188. |
There is an example in spec x := []int{1, 2, 3}
i := 0
i, x[i] = 1, 2 // set i = 1, x[0] = 2 which indicates the example in #23188 shouldn't panic arr := []int{1, 2}
arr, arr[len(arr)-1] = arr[:len(arr)-1], 3 [update] // The preparation phase:
P0 := &i; P1 := &x[0]
R0 := 1; R1 := 2
// The elementary form: *P0, *P1 = R0, R1
// The carry-out phase:
*P0 = R0
*P1 = R1 The explanation for |
edit the comment by adding 4th case and modifying the 2nd case. |
Please answer these questions before submitting your issue. Thanks!
go version
)?gccgo version 4.9.3
go env
)?Ubuntu 14.04 on AWS t2.micro
If possible, provide a recipe for reproducing the error.
A complete runnable program is good.
A link on play.golang.org is best.
Deleting from a slice as suggested by https://github.com/golang/go/wiki/SliceTricks, raises an index out of bound error when compiling with gccgo. The following toy program works fine using GC but not with GCCGO:
https://play.golang.org/p/f039m1h7Z1
Note: while this toy example is a slice of ints, I'm using the delete code for removing an entry from a slice of pointers. The bug is in
xs[len(xs)-1] = 0
.[0 1 3 4]
The text was updated successfully, but these errors were encountered: