This article will explain expression evaluation orders in all kinds of scenarios.
f(x, y[n])
,
f()
is evaluated later than its depended expressions,
including f
, x
and y[n]
.
y[n]
is later than
the evaluations of n
and y
.
Please read program code element initialization order for another example on package-level variable initialization orders.
When a package is loaded at run time, Go runtime will try to initialize uninitialized package-level variables which have no dependencies on uninitialized variables, by their declaration order, until no variables are initialized in such a process. For a successfully compiled Go program, there should be no uninitialized variables after all such processes end.
Package-level variables appearing as blank identifiers are treated like any other variables in the initialization process.
For example, in the following program, variablea
depends b
,
and variables c
and _
depend on a
.
So
b
,
which is the first package-level variable without dependencies on other package-level variables.
a
.
After b
is initialized,
a
is the first package-level variable
without dependencies on uninitialized package-level variables.
_
and c
.
After a
and b
are initialized,
_
and c
both don't depend on uninitialized package-level variables.
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
}
The above program prints 5 6 5
.
Multiple variables on the left-hand side of a variable specification
initialized by single multi-valued expression on the right-hand side
are initialized together. For example, for a package-level variable
declaration var x, y = f()
, variables x
and y
will be initialized together. In other words, no other variables will
be initialized between them.
var m, n = expr1, expr2
is equivalent to
var m = expr1
var n = expr2
If hidden dependencies exist between variables, the initialization order between those variables is unspecified. In the following example (copied from Go specification),
a
will be initialized after b
for sure,
x
is initialized before b
, between
b
and a
, or after a
, is not specified.
sideEffect()
is called (before
or after x
is initialized) is also not specified.
// x has a hidden dependency on a and b
var x = I(T{}).ab()
// Assume sideEffect is unrelated to x, a, and 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} }
Please note that, Go specification doesn't compulsively specify the compilation order of the source files in a package, so try not to put some package-level variables into different source files in a package if there are complicate dependency relations between those variables; otherwise a variable might be initialzied to different values by different Go compilers.
In a bool expression a && b
, the right operand expression b
will be evaluated
only if the left operand expression a
is evaluated as true
.
So b
will be evaluated, if it needs to be evaluated, after the evaluation of a
.
In a bool expression a || b
, the right operand expression b
will be evaluated
only if the left operand expression a
is evaluated as false
.
So b
will be evaluated, if it needs to be evaluated, after the evaluation of a
.
The just described order is called the usual order.
Please note that an explicit value conversion T(v)
is not a function call.
[]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 (executed) before fb()
.
However, the following the evaluation orders are unspecified in Go specification:
x
(or y
) and fa()
(or fb()
).
x
, y
, fa
and fb
.
y[z.f()], ok = g(h(a, b), i()+x[j()], <-c), k()
where
c
is a channel expression and will be evaluated to a channel value.
g
, h
, i
, j
and k
are function expressions.
f
is a method of expression z
.
z.f()
→h()
→i()
→j()
→<-c
→g()
→k()
.
h()
is evaluated after the evaluations of expressions h
, a
and b
.
y[]
is evaluated after the evaluation of z.f()
.
z.f()
is evaluated after the evaluation of expression z
.
x[]
is evaluated after the evaluation of j()
.
y
, z
, g
,
h
, a
, b
, x
,
i
, j
, c
and k
.
y[]
, x[]
and <-c
.
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 element 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 unspecified.
n := map[int]int{a: f()}
Later, we may call the first phase as evaluation phase and the second phase as carry-out phase.
Go specification doesn't specify clearly whether or not the assignments carried-out during the second phase may affect the expression evaluation results got in the first phase, which ever caused some disputes. So, here, this article will explain more on the evaluation orders in value assignments.
Firstly, let's clarify that the assignments carried-out during the second phase don't affect the expression evaluation results got at the end of the first phase.
To make the following explanations convenient, we assume that the container (slice or map) value of an index destination expression in an assignment is always addressable. If it is not, we can think the container value has already been saved in and replaced by a temporary addressable container value before carrying out the second phase.
At the time of the end of the evaluation phase and just before the carry-out phase starts, each destination expression on the left of an assignment has been evaluated as its elementary form. Different destination expressions have different elementary forms.c[k]
,
then its elementary form is (*cAddr)[k]
,
where cAddr
is a pointer pointing to c
.
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 := b; R1 := a
// 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; T2 := i
R0 := 1; R1 := 2
// Now, T2 == 0
// The elementary form: *P0, (*P1)[T2] = R0, R1
// The carry-out phase:
*P0 = R0
(*P1)[T2] = 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; PS3 := &s; T3 := 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, (*PS3)[T3] = R0, R1, R2, R3
// The carry-out phase:
*P0 = R0
(*PM1)[K1] = R1
*P2 = R2
(*PS3)[T3] = 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 recommended to use complex multi-value assignments in Go, for their readabilities are not good and they have negative effects on both compilation speed and execution performance.
As mentioned above, not all orders are specified in Go specification for value assignments, so some bad written code may produce different results. In the following example, the expression order ofx+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
or 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())
}
switch-case
Code Blocksswitch-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 be evaluated and compared with the switch expression one by one,
until a comparison results in 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 in true
.
So f(8)
, f(9)
and f(10)
will be not evaluated in this example.
f(3) is called.
f(4) is called.
f(5) is called.
f(6) is called.
f(7) is called.
select-case
Code Blocks
When executing a select-case
code block, before entering a branch code block,
all the channel operands of receive operations and the operands of send statements
involved in the select-case
code block are evaluated exactly once,
in source order (from top to bottom, from left to right).
Note, the target expression being assigned to by a receive case
operation will only be evaluated if that receive operation is selected later.
*fptr("aaa")
will never get 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 프로젝트는 Github 에서 호스팅됩니다. 오타, 문법 오류, 부정확한 표현, 설명 결함, 코드 버그, 끊어진 링크와 같은 모든 종류의 실수에 대한 수정 사항을 제출하여 Go 101을 개선을 돕는 것은 언제나 환영합니다.
주기적으로 Go에 대한 깊이 있는 정보를 얻고 싶다면 Go 101의 공식 트위터 계정인 @go100and1을 팔로우하거나 Go 101 슬랙 채널에j가입해주세요.
reflect
표준 패키지sync
표준 패키지sync/atomic
표준 패키지