평가 순서(Order of evaluation)

평가 순서(Order of evaluation)

Go의 코드는 겉보기엔 위에서 아래로, 왼쪽에서 오른쪽으로 실행될 거라고 느껴지는데요. 맞는 말이지만, 그 규칙이 어디까지 적용되고 어디서부터는 보장되지 않는지가 이 섹션의 핵심이에요. 특히 함수 호출이 섞인 표현식에서는 "언제 어떤 것이 호출된다"라는 보장이 실제 프로그램의 결과를 좌우하죠. 지금부터 그 규칙을 차근차근 풀어볼게요.

출처: Go Specification

본문

패키지 레벨에서는, 초기화 의존성(initialization dependencies)이 변수 선언 안에 있는 각 초기화 표현식의 평가 순서를 결정해요.

그 외의 경우에는, 표현식(expression)이나 대입(assignment), return 문의 피연산자들을 평가할 때, 모든 함수 호출과 메서드 호출, receive 연산, 그리고 이진 논리 연산(binary logical operations)이 어휘적(lexical) 왼쪽-에서-오른쪽 순서로 평가돼요.

예를 들어 다음 (함수 내부의) 대입문에서,

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

함수 호출과 통신은 f(), h()(단, zfalse로 평가될 때), i(), j(), <-c, g(), k() 순서로 일어나요.

하지만 이런 사건들이 x의 평가·인덱싱, 그리고 yz의 평가와 비교해서 어떤 순서인지는 — 어휘적으로 요구되는 경우를 제외하고는 — 지정되어 있지 않아요. 예를 들어 g는 그 인자들이 평가되기 전에는 호출될 수 없어요.

a := 1
f := func() int { a++; return a }
x := []int{a, f()}            // x may be [1, 2] or [2, 2]: evaluation order between a and f() is not specified
m := map[int]int{a: 1, a: 2}  // m may be {2: 1} or {2: 2}: evaluation order between the two map assignments is not specified
n := map[int]int{a: f()}      // n may be {2: 3} or {3: 3}: evaluation order between the key and the value is not specified

패키지 레벨에서는 초기화 의존성이 각 초기화 표현식에 대한 왼쪽-에서-오른쪽 규칙을 덮어쓰지만(override), 각 표현식 내부의 피연산자들 사이에서는 덮어쓰지 않아요:

var a, b, c = f() + v(), g(), sqr(u()) + v()

func f() int        { return c }
func g() int        { return a }
func sqr(x int) int { return x*x }

// functions u and v are independent of all other variables and functions

이때 함수 호출은 u(), sqr(), v(), f(), v(), g() 순서로 일어나요.

단일 표현식 안에서의 부동소수점 연산(Floating-point operations)은 연산자의 결합성(associativity)에 따라 평가돼요. 명시적인 괄호는 기본 결합성을 무시하고(overriding) 평가에 영향을 줘요. x + (y + z) 라는 표현식에서는 y + z 덧셈이 x를 더하기 전에 먼저 수행돼요.

더 알아보기

  • 패키지 레벨 초기화의 의존성 규칙 전체는 Go 스펙의 Package initialization 섹션에서 다뤄요.
  • 평가 순서가 실제 컴파일러에서 어떻게 보장되는지, 그리고 왜 이 규칙을 의존해서는 안 되는지는 함께 읽어보면 좋아요.
  • 이 섹션은 표현식(Expressions) 장에 이어 Statements 장으로 넘어가기 직전의 내용이에요. 다음으로 문(statement)의 실행 제어를 살펴보세요.