본문 바로가기
golang

golang에서 for loop scoping

by marble25 2023. 10. 2.

go 1.22버전에서 for loop 내의 변수 범위에 대한 업데이트가 있어서 공식 블로그에 올라온 글을 번역하면서 정리해보고자 한다.

관련 문서

https://go.dev/blog/loopvar-preview

https://go.googlesource.com/proposal/+/master/design/60078-loopvar.md

문제

go로 코드를 짜면 다음과 같이 실수를 저지르는 경우가 많을 것이다.

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

우리가 원하는 “a”, “b”, “c”가 아닌, “c”, “c”, “c”가 나올 것이다. 이는 같은 변수를 가리키고 있기 때문이다.

위의 버그를 막기 위해서는 v := v를 추가해야 한다.

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
				v := v
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

업데이트

Go 1.22 버전에서 for loop를 per-loop scope가 아닌, per-iteration scope로 변경하려고 계획했다. 이 변경으로 위의 예제들을 수정하여 버그가 없는 Go 프로그램이 되며, 이러한 실수로 인해 발생하는 생산 문제를 해결하고, 사용자가 코드를 불필요하게 변경할 필요가 없는 부정확한 도구의 필요성을 제거하려 한다.

기존 코드와의 하위 호환성을 보장하기 위해 새로운 semantics는 go.mod 파일에서 go 1.22 또는 이후를 선언한 모듈에 포함된 패키지에서만 적용된다. 기존 코드는 현재와 정확히 동일한 의미를 가지게 유지된다: 이 업데이트는 새로운 코드나 업데이트된 코드에만 적용된다.

테스팅

func TestAllEvenBuggy(t *testing.T) {
    testCases := []int{1, 2, 4, 6}
    for _, v := range testCases {
        t.Run("sub", func(t *testing.T) {
            t.Parallel()
            if v&1 != 0 {
                t.Fatal("odd v", v)
            }
        })
    }
}

Go 1.21에서는 t.Parallel이 모든 loop가 끝날 때까지 block한 후 모든 subtest가 parallel하게 수행되도록 하기 때문에 성공한다. loop가 끝나면, v는 무조건 6이기 때문에 6이 even인지 체크 후에 성공하게 된다. 당연하게도 1이 짝수가 아니기 때문에 원래 실패해야 한다.

'golang' 카테고리의 다른 글

[TIL] Go buffer read  (0) 2023.10.27
Go convention 정리  (0) 2023.10.03
Go 상속 vs 구성  (1) 2023.10.01
Go context 파헤치기  (0) 2023.09.29
goroutine 파헤치기  (0) 2023.09.23