tl;dr

どんな言語でも評価順序は注意するべきだし評価順序が気になるコードは書かないように気をつけるべき。

golangの評価順序は定められていて、オペランド, 代入, 関数, メソッド, 通信は左から評価される。 変数の値は上記の評価とは別に評価される。 どのみち1行内で副作用を伴う関数・メソッドや通信は避けたほうが良い。

example

下みたいなコードは地雷だ。チャンネルと関数呼び出しの同居

go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	format = "init string(%d)\n"
	mes    = make(chan int)
	wg     = sync.WaitGroup{}
	base   = 10
)

func getTarget() string {
	return format
}
func init() {
	go func() {
		for {
			fmt.Printf(getTarget(), base + <-mes)
			wg.Done()
		}
	}()
}
func main() {
	wg.Add(1)
	mes <- 10
	format = "last string(%d)\n"
	time.Sleep(time.Second)
	wg.Add(1)
				mes <- 10
	wg.Wait()
}

ちなみに右辺値の変数の評価は事前に解決するため。 下のような複数代入でも変数評価は事前に行われ影響が伝播しない。 head,tail; init,lastの複数代入

go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func main() {
	list := []int{1, 2, 3, 4, 5}
	var h int
	for len(list) > 0 {
		h, list = list[0], list[1:]
		fmt.Printf("head:%d, tail:%#v\n", h, list)
	}
	list = []int{1, 2, 3, 4, 5}
	var l int
	for c := 0; c < 5; c++ {
		list, l = list[:len(append(list, 6))-1], list[len(list)-1]
		fmt.Printf("pre:%d, last:%#v\n", list, l)
	}
	start := 0
	start, end := start-1, start+1
	fmt.Printf("start:%d, end:%d\n", start, end)
}