# 表达式估值顺序规则

本文将解释各种情形下表达式的估值顺序。

### 一个表达式将在其所依赖的其它表达式估值之后进行估值

这属于常识，没什么好解释的。一个显然的例子是一个表达式将在组成它的子表达式都估值之后才能进行估值。 比如，在一个函数调用`f(x, y[n])`中，

- `f()`将在它所依赖的子表达式`f`、`x`和`y[n]`估值之后进行估值；
- 表达式`y[n]`将在它所依赖的子表达式`n`和`y`估值之后进行估值。

另外，程序代码要素初始化顺序章节中提供了一个关于包级变量初始化顺序的例子。

### 包级变量初始化顺序

在运行时刻，当一个包被加载的时候，不依赖于任何其它未初始化包级变量的未初始化包级变量将按照它们在代码中的声明顺序被初始化，直到此过程中不再有任何包级变量被初始化。 对于一个成功编译了的Go程序，当所有这样的过程结束之后，所有的包级变量都应该被初始化了。

在包级变量初始化过程中，呈现为空标识符的包级变量和其它包级变被同等对待。

举个例子，在下面的这个程序中，变量`a`依赖于变量`b`，变量`c`和`_`依赖于变量`a`。 所以

1. 第一个被初始化的变量是`b`，因为它是第一个不依赖于其它变量的包级变量。
2. 第二个被初始化的变量是`a`，因为在变量`b`初始化之后，变量`a`是第一个不再依赖于未初始化包级变量的包级变量。.
3. 第三和第四个被初始化的变量是`_`和`c`。 在变量`a`和`b`初始化之后，标记变量`_`和`c`均不再依赖于未初始化包级变量。

    package main

    import "fmt"

    var (
    	_ = f()
    	a = b / 2
    	b = 6
    	c = f()
    )

    func f() int {
    	a++
    	return a
    }

    func main() {
    	fmt.Println(a, b, c) // 5 6 5
    }

上面这个程序打印出`5 6 5`。

通过一个多值表达式源值来初始化的多个包级变量将被一起初始化。 比如，在包级变量声明`var x, y = f()`中，变量`x`和`y`将被一起初始化。 或者说，在它们的初始化之间不会有其它包级变量被初始化。

在初始化所有包级变量之前，含有多个源值表达式的包级变量声明将被拆解为多个单源值表达式变量声明。 比如

    var m, n = expr1, expr2

和

    var m = expr1
    var n = expr2

是等价的。

如果一些包级变量之间存在着编译器难以觉察的依赖关系，则这些包级变量的初始化顺序是未定义的，依赖于具体编译器实现。 在下面这个Go白皮书中的例子中，

- 变量`a`肯定在变量`b`之后初始化；
- 但是变量`x`有可能在变量`b`之前、或者在变量`b`和变量`a`之间、或者在变量`a`之后初始化，取决于具体的编译器实现；
- 函数`sideEffect()`有可能在变量`x`初始化之前或者之后被调用，取决于具体的编译器实现。

    // x是否依赖于a和b，不同的编译器有不同的见解。
    var x = I(T{}).ab()
    // 假设函数sideEffect和x、a以及b均无关系。
    var _ = sideEffect()
    var a = b
    var b = 42

    type I interface    { ab() []int }
    type T struct{}
    func (T) ab() []int { return []int{a, b} }

注意：因为一个代码包中常常包含若干个代码源文件，而Go白皮书并没有强制规定编译器按照如何顺序来编译一个代码包中的源文件， 所以尽量不要让一个代码包中不同源文件中的包级变量存在复杂的依赖关系；否则不同的编译器可能会将一些包级变量初始化为不同结果。

另请注意：Go官方工具链的某些版本未正确实现在本节中描述的规则。

- Go官方工具链1.22.0-1.22.4版本中的一个bug。
- 1.23版本之前Go官方工具链中的一个bug。

### 布尔（逻辑）运算表达式中的操作数子表达式的估值顺序

在一个布尔运算`a && b`中，右操作数表达式`b`只在左操作数表达式`a`被估值为`true`的时候才会被估值。 所以，操作数表达式`b`如果需要被估值的话，它肯定在操作数表达式`a`之后估值。

在一个布尔运算`a || b`中，右操作数表达式`b`只在左操作数表达式`a`被估值为`false`的时候才会被估值。 所以，操作数表达式`b`如果需要被估值的话，它肯定在操作数表达式`a`之后估值。

### 通常估值顺序（The Usual Order）

Go白皮书这样描述通常估值顺序：
...，当估值一个表达式、赋值语句或者函数返回语句中的操作数时，所有的函数调用、方法调用和通道操作将按照它们在代码中的出现顺序进行估值。

注意：一个显式类型转换`T(v)`不属于函数调用。

举个例子，在表达式`[]int{x, fa(), fb(), y}`中，假设`x`和`y`是两个`int`类型的变量，`fa`和`fb`是两个返回值为`int`类型的函数，则调用`fa()`保证在调用`fb()`之前执行。 但是，下面这两个估值顺序没有在Go白皮书中指定：

- 变量`x`（或者`y`）和调用`fa()`（或者`fb()`）的相对估值顺序；
- 变量`x`、变量`y`、函数值`fa`和函数值`fb`的相对估值顺序。

下面是Go白皮书中提到的另一个例子：

    y[z.f()], ok = g(h(a, b), i()+x[j()], <-c), k()

在此赋值语句中，

- `c`是一个通道表达式，它将被估值为一个通道值；
- `g`、`h`、`i`、`j`和`k`是一些函数表达式，它们将被估值为一些函数值；
- `f`是表达式`z`值的一个方法。

综合考虑上一节和本节上面已经提到的规则，编译器应该保证下列在运行时刻的估值顺序：

- 此赋值中涉及到的函数调用、方法调用和通道操作必须按照这样的顺序执行：`z.f()`→`h()`→`i()`→`j()`→`<-c`→`g()`→`k()`；
- 调用`h()`在表达式`h`、`a`和`b`估值之后调用；
- `y[]`在方法调用`z.f()`执行之后被估值；
- 方法调用`z.f()`在表达式`z`估值之后执行；
- `x[]`在调用`j()`执行之后被估值。

然而，下列次序在Go白皮书中未指定，它们依赖于具体编译器实现：

- 表达式`y`、`z`、`g`、`h`、`a`、`b`、`x`、`i`、`j`、`c`和`k`之间的相对估值顺序；
- 表达式`y[]`、`x[]`和`<-c`之间的相对估值顺序。

根据上述通常估值顺序规则，我们知道下面声明的变量`x`、`m`和`n`的初始值可能将出现歧义。

    	a := 1
    	f := func() int { a++; return a }

    	// x可能初始化为[1, 2]或者[2, 2]，
    	// 因为a和f()的相对估值顺序未指定。
    	x := []int{a, f()}

    	// m可能初始化为{2: 1}或者{2: 2}，
    	// 因为两个映射元素的赋值顺序未指定。
    	m := map[int]int{a: 1, a: 2}

    	// n可能初始化为{2: 3}或者{3: 3}，
    	// 因为a和f()的相对估值顺序未指定。
    	n := map[int]int{a: f()}

### 赋值语句中的表达式估值和赋值执行顺序

除了上面介绍的规则，Go白皮书对赋值语句中的表达式估值和各个单值赋值执行顺序进行了更多描述（原英文描述不是十分精确，在翻译过程中对之略加改进）：
一条赋值语句的执行分为两个阶段。 首先，做为目标值的元素索引表达式中的容器值表达式和索引值表达式、做为目标值的指针解引用表达式中的指针值表达式、以及此赋值语句中的其它非目标值表达式将按照上述通常估值顺序估值。 然后，各个单值赋值将按照从左到右的顺序执行。

以后，我们可以称第一个阶段为估值阶段，称第二个阶段为实施阶段。

Go白皮书并没有清楚地说明在第二个阶段中发生的赋值操作是否会对在第一个阶段结尾确定下来的各个子表达式的估值结果造成影响，此举曾造成了一些 争议。 所以，这里下面将对赋值语句中的表达式估值顺序做出一些补充解释。

首先，先明确一下：第二个阶段中发生的赋值操作绝不会对在第一个阶段结尾确定下来的各个子表达式的估值结果造成影响.

为了方便下面的解释，对于一个赋值语句，我们假设一个做为目标值的容器（切片或者映射）元素索引表达式中的映射值总是可寻址的。 如果它是不可寻址的，我们可以认为在实施第二个阶段之前，此容器值已经被赋给了一个临时变量（可寻址的）并且在此赋值语句中此容器值已经被此临时变量取代。

在估值阶段结束之后、实施阶段开始之前的时刻，赋值语句中的每个目标值表达式都已经被估值为它的最基本形式。 不同风格的目标值表达式有着不同的最基本形式：

- 如果一个目标值表达式是一个空标识符，则它的最基本形式依旧是一个空标识符；
- 如果一个目标值表达式是一个容器（数组或者切片或者映射）元素索引表达式`c[k]`，则它的最基本形式为`(*cAddr)[k]`，其中一个`cAddr`为指向`c`的指针；
- 对于其它情形的任何一个目标值表达式，它必然是可寻址的，则它的最基本形式为它的地址的解引用形式。

假设`a`和`b`为两个可寻址的同类型变量，则下面的赋值语句

    	a, b = b, a

将按照如下步骤执行：

    // 估值阶段
    P0 := &a; P1 := &b
    R0 := b; R1 := a

    // 最基本形式：*P0, *P1 = R0, R1

    // 实施阶段
    *P0 = R0
    *P1 = R1

下面是另外一个例子，其中`x[0]`而不是`x[1]`被修改了。

    	x := []int{0, 0}
    	i := 0
    	i, x[i] = 1, 2
    	fmt.Println(x) // [2 0]

上例中的第3行的分解执行步骤如下：

    // 估值阶段
    P0 := &i; P1 := &x; T2 := i
    R0 := 1; R1 := 2
    // 到这里，T2 == 0

    // 最基本形式：*P0, (*P1)[T2] = R0, R1

    // 实施阶段
    *P0 = R0
    (*P1)[T2] = R1

下面是一个略为复杂一点的例子。

    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]
    }

上例中的第10行的分解执行步骤如下：

    // 估值阶段
    P0 := &s; PM1 := &m; K1 := "Go"; P2 := p; PS3 := &s; T3 := 2
    R0 := []int{2, 2, 2}; R1 := s[1]; R2 := m["Go"]; R3 := 5
    // 到这里，R1 == 1, R2 == 0

    // 最基本形式：*P0, (*PM1)[K1], *P2, (*PS3)[T3] = R0, R1, R2, R3

    // 实施阶段
    *P0 = R0
    (*PM1)[K1] = R1
    *P2 = R2
    (*PS3)[T3] = R3

下面这个例子将一个切片中的所有元素循环顺移了一位。

    	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]

另一个例子：

    	x := []int{123}
    	x, x[0] = nil, 456        // 此句不会发生恐慌
    	x, x[0] = []int{123}, 789 // 此句将产生恐慌

尽管使用复杂的多值赋值语句是合法的，但是在实践中并不推荐使用，因为复杂多值赋值语句的可读性不高，编译速度较慢，并且执行效率相对略低。

上面已经提到了，并非所有的估值顺序都在Go白皮书中指定清楚了，所以不同的Go编译器对这些未指定的估值顺序有着自己的理解。 一些跨编译器兼容性不好的代码将导致使用不同的编译器编译的程序的运行结果不同。 在下面这个例子中，表达式`x+1`和`f(&x)`的估值顺序是编译器相关的，所以此程序输出`100 99`或者`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)
    }

下面是另一个可能输出不同结果的程序。它可能输出`1 7 2`、`1 8 2`或者`1 9 2`，取决于不同的编译器实现。

    package main

    import "fmt"

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

### `switch-case`流程控制代码块中的表达式估值顺序

`switch-case`流程控制代码块中的表达式估值顺序已经在前面的文章中大致描述过了。 这里仅仅展示一个例子。简单来说，在进入一个分支代码块之前，各个`case`关键字后跟随的表达式将按照从上到下和从左到右的顺序进行估值，直到某个比较结果为`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):
    	}
    }

在运行时刻，各个`f()`调用将按照传给它们参数的大小顺序进行估值，直到和调用`f(7)`的比较结果为`true`为止。 所以调用`f(8)`、`f(9)`和`f(10)`将不会被估值。

输出结果：

    f(3) is called.
    f(4) is called.
    f(5) is called.
    f(6) is called.
    f(7) is called.

### `select-case`流程控制代码块中的表达式估值顺序

当执行一个`select-case`流程控制代码块时，各个`case`关键值后跟随的所有通道操作中的通道表达式和所有通道发送操作中的发送值表达式都将被按照它们在代码中的出现次序（从上到下从左到右）估值一次。

注意：以通道接收操作做为源值的赋值语句中的目标值表达式只有在此通道接收操作被选中之后才会被估值。

比如，在下面这个例子中，表达式`*fptr("aaa")`将永不会得到估值，因为它对应的通道接收操作`<-fchan("bbb", nil)`是个不可能被选中的阻塞操作。

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

上例的输出结果：

    bbb
    ddd
    eee
    fff
    ggg
    hhh
    ccc

注意，表达式`*fptr("ccc")`是上例中最后一个被估值的表达式。 它在对应的数据接收操作`<-fchan("ddd", c)`被选中之后才会进行估值。