Skip to content

An ambiguity of (or dispute on) the evaluation order of LHS (left hand side) items in a multi value assignment

Go101 edited this page Sep 25, 2018 · 8 revisions

Let's view several simple Go code examples firstly.

The first one:

package main
 
import "fmt"
 
func main() {
        a := []int{1, 2}
        a, a[0] = []int{5, 6}, 3
        fmt.Println(a)
}

What do you expect the above program will print? [5 6] or [3 6]? FYI, gc (the standard Go compiler) prints [5 6], however gccgo prints [3 6].

The second example:

package main
 
import "fmt"
 
func main() {
        arr := []int{1, 2}
        arr, arr[len(arr)-1] = arr[:len(arr)-1], 3
        fmt.Println(arr)
}

Will the second program panic or not? FYI, gc prints [1] without panicking, but gccgo panics by thinking "index out of range" happens in arr[len(arr)-1].

There is a similar example to the second one.

The third example:

package main

import "fmt"

func main() {
	var s []int
	s, s[0] = []int{1, 2, 3}, 9
	fmt.Println(s)
}

Unsurprisingly, gc panics for the third program, and gccgo prints [9 2 3] without panicking.

Ok, let's talk the main topic. Is the reason of the inconsistencies between gc and gccgo for above programs same as the reason for the inconsistencies for the examples in the article some evaluation orders in multi value assignments are unspecified?

The answer is partly yes. Whereas all Go core team members agree that the inconsistencies for the examples in the article some evaluation orders in multi value assignments are unspecified are caused by some evaluation orders are unspecified, some Go core team members think gccgo does it wrongly for the examples mentioned above. In other words, they think the rules are well defined here. But gccgo maintainer Ian Lance Taylor doesn't think so. (update: Ian accepted the opinion of the other in the end.)

The core of the dispute is whether or not the slice index items on the left side of an assignment should be evaluated to the element level or the container level. For example, the dispute is which one of the following two step flows should be adopted for the third example.

Step flow one (adopted by gc):

	// s, s[0] = []int{1, 2, 3}, 9
	x := s
	s = []int{1, 2, 3}
	x[0] = 9

Step flow two (adopted by gccgo):

	// s, s[0] = []int{1, 2, 3}, 9
	s = []int{1, 2, 3}
	s[0] = 9

Personally, I prefer the gc interpretation. However, maybe Go specification is really a little ambiguous here:

... 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.

Nothing more to say. Just an advice for Go programming in practice, don't use multi-value assignments like the ones in this article. Split each of such multi-value assignments into multiple single-value assignments instead.

Finally, another reason we should try not to use multi-value assignments in Go. The following program should print 1 for len(m), but the current gc (v1.10.3) prints it as 0 instead (gccgo does it right). This is a known bug of gc.

package main

import "fmt"

func main() {
	var m = map[int]int{}
	var p *int

	defer func() {
		fmt.Println(recover())
		fmt.Println(len(m)) // 0
	}()
	m[2], *p = 1, 2
}

[Update]: Ian accepted the opinion of the other in the end. So this dispute should be cleared since about gccgo 9.

Clone this wiki locally