Expression Evaluation Orders

This article will explain expression evaluation orders in all kinds of scenarios.

An Expresssion Is Evaluated After The Expressions It Depends On

This is easy to comprehend. An apparent example is an expression is evaluated later than its sub-expressions. For example, in a function call f(x, y[n]),

Please read program resoruce initialization order for another example on package-level variable initialization orders.

The Usual Order

For the evaluations within a function body, Go specification says
..., when evaluating the operands of an expression, assignment, or return statement, all function calls, method calls, and (channel) communication operations are evaluated in lexical left-to-right order.

The just described order is called the usual order.

Please note that an explicit value conversion T(x) is not a function call.

For example, in an expression []int{x, fa(), fb(), y}, assume x and y are two variables, fa and fb are two functions, then the call fa() is guaranteed to be evaluated before fb(). However, the following the evaluation orders are unspecified in Go specification: Another example, the following assignment, is demoed in Go specification.
y[z.f()], ok = g(h(a, b), i()+x[j()], <-c), k()
where Also considering the rule mentioned in the last section, compilers should guarantee the following evaluation orders at run time. However, the following orders (and more others) are not specified. By the above introduced rules, we know the following delcared variables x, m and n (also demoed in Go specification) will be initialized with ambiguous values.
	a := 1
	f := func() int { a++; return a }

	// x may be [1, 2] or [2, 2]: evaluation order 
	// between a and f() is not specified.
	x := []int{a, f()}

	// m may be {2: 1} or {2: 2}: evaluation order
	// between the two map assignments is not specified.
	m := map[int]int{a: 1, a: 2}

	// n may be {2: 3} or {3: 3}: evaluation order
	// between the key and the value is not specified.
	n := map[int]int{a: f()}

Evaluation Orders In Assignment Statements

Beside the usual order, Go specification specifies more on the expression evaluation order in an assignment:
The assignment proceeds in two phases. 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.

Later, we call the first phase as evaluation phase and the second phase as carry-out phase. At the evaluation phase, all expressions involved in that assignment are evaluated by the rules mentioned in last two sections.

Go specification doesn't specify clearly how the operands in the mentioned expressions on the left should be exactly evaluated, which ever caused some disputes. So, hrere, this article will explain more on the evaluation orders in value assignments.

To make the following explanations convenient, if the map value of a map index destination expression in an assignment is a not addressable, we can think the map value has already been saved in and replaced by a temporary addressable map value before executing the assignment.

In the end of the evaluation phase, just before the carry-out phase starts, each destination expression on the left of an assignment will be further evaluated as its elementary form. Different destination expressions have different elementary forms. Assume a and b are two addressable variables of the same type, the following assignment
	a, b = b, a
will be executed like the following steps:
// The evaluation phase:
P0 := &a; P1 := &b
R0 := a; R1 := b

// The elementary form: *P0, *P1 = R0, R1

// The carry-out phase:
*P0 = R0
*P1 = R1

Here is another example, in which x[0] instead of x[1] is modified.
	x := []int{0, 0}
	i := 0
	i, x[i] = 1, 2
	fmt.Println(x) // [2 0]
The decomposed execution steps for the line 3 shown below are like:
// The evaluation 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

An example which is a little more complex.
package main

import "fmt"

func main() {
	m := map[string]int{"Go": 0}
	s := []int{1, 1, 1}; olds := s
	n := 2
	p := &n
	s, m["Go"], *p, s[n] = []int{2, 2, 2}, s[1], m["Go"], 5
	fmt.Println(m, s, n) // map[Go:1] [2 2 2] 0
	fmt.Println(olds)    // [1 1 5]
}

The decomposed execution steps for the line 10 shown below are like:
// The evaluation phase:
P0 := &s; PM1 := &m; K1 := "Go"; P2 := p; P3 := &s[2]
R0 := []int{2, 2, 2}; R1 := s[1]; R2 := m["Go"]; R3 := 5
// now, R1 == 1, R2 == 0

// The elementary form: *P0, (*PM1)[K1], *P2, *P3 = R0, R1, R2, R3

// The carry-out phase:
*P0 = R0
(*PM1)[K1] = R1
*P2 = R2
*P3 = R3

The following example rotates all elements in a slice for one index.
	x := []int{2, 3, 5, 7, 11}
	t := x[0]
	var i int
	for i, x[i] = range x {}
	x[i] = t
	fmt.Println(x) // [3 5 7 11 2]

Another example:
	x := []int{123}
	x, x[0] = nil, 456        // will not panic
	x, x[0] = []int{123}, 789 // will panic

Although it is legal, it is not recommanded to use complex multi-value assignments in Go, for their readabilities are not good and they have negative effects on both complication speed and execution performance.

Not all orders are specified in Go specification for value assignments, so some bad written code may result different results. In the following example, the expression order of x+1 and f(&x) is not specified. So the example may print 100 99 or 1 99.
package main

import "fmt"

func main() {
	f := func (p *int) int {
		*p = 99
		return *p
	}
	
	x := 0
	y, z := x+1, f(&x)
	fmt.Println(y, z)
}

The following is another example which will print ambiguous results. It may print 1 7 2, 1 8 2 and 1 9 2, depending on different compiler implementations.
package main

import "fmt"

func main() {
	x, y := 0, 7
	f := func() int {
		x++
		y++
		return x
	}
	fmt.Println(f(), y, f())
}

Expression Evaluation Orders In switch-case Code Blocks

The expression evaluation order in a switch-case code block has been described before. Here just shows an example. Simply speaking, before a branch code block is entered, the case expressions will evaluated and compared with the switch expression one by one, until a comparation results true.
package main

import "fmt"

func main() {
	f := func(n int) int {
		fmt.Printf("f(%v) is called.\n", n)
		return n
	}
	
	switch x := f(3); x + f(4) {
	default:
	case f(5):
	case f(6), f(7), f(8):
	case f(9), f(10):
	}
}

At run time, the f() calls will be evaluated by the order from top to bottom and from left to right, until a comparison results true. So f(8), f(9) and f(10) will be not evaluated in this example.

The output:
f(3) is called.
f(4) is called.
f(5) is called.
f(6) is called.
f(7) is called.

Expression Evaluation Orders In select-case Code Blocks

For all the cases in a select-case code block, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon starting executing the select statement.

The left-hand-side expression of each receive case operation will only be evaluated if that receive operation is selected. The left-hand-side expressions of the unselected receive statements will never be evaluated.

In the following example, the expression *fptr("aaa") will never be evaluated, for its corresponding receive operation fchan("bbb", nil) will not be selected.
package main

import "fmt"

func main() {
	c := make(chan int, 1)
	c <- 0
	fchan := func(info string, c chan int) chan int {
		fmt.Println(info)
		return c
	}
	fptr := func(info string) *int {
		fmt.Println(info)
		return new(int)
	}
	
	select {
	case *fptr("aaa") = <-fchan("bbb", nil): // blocking
	case *fptr("ccc") = <-fchan("ddd", c):   // non-blocking
	case fchan("eee", nil) <- *fptr("fff"):  // blocking
	case fchan("ggg", nil) <- *fptr("hhh"):  // blocking
	}
}
The output of the above program:
bbb
ddd
eee
fff
ggg
hhh
ccc

Note that the expression *fptr("ccc") is the last evaluated expression in the above example. It is evaluated after its corresponding receive operation <-fchan("ddd", c) is selected.


The Go 101 project is hosted on both Github and Gitlab. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.

Support Go 101 by playing Tapir's games. Cryptocurrency donations are also welcome:
Bitcoin: 1xucQbv5jujFPPwhyg395ri5yV71hx9g9
Ethereum: 0x5dc4aa2c2bbfaadae373dadcfca11b3358912212