Go에서 익명 함수(함수 리터럴)는 클로저를 생성할 수 있으며, 이는 주변 스코프의 변수를 종료된 후에도 접근할 수 있게 해줍니다. 이러한 클로저는 올바르도록 작동하기 위해 필요할 경우 힙에 메모리를 할당합니다(탈출 분석을 통해 감지됨).
예제:
func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } a := adder() printf("%d ", a(5)) // 5 printf("%d ", a(10)) // 15
특징:
이 코드는 무엇을 출력할까요?
func main() { fs := []func(){} for i := 0; i < 3; i++ { fs = append(fs, func() { fmt.Println(i) }) } for _, f := range fs { f() } }
많은 사람들이 0, 1, 2를 출력할 것이라고 답할 것이지만, 결과는:
3
3
3
모든 클로저가 동일한 변수 i를 참조하며, 루프 종료 후 그 값은 3입니다.
올바른 방법: 루프 본문에서 변수의 복사본을 캡쳐하는 것입니다:
for i := 0; i < 3; i++ { v := i // 새로운 변수 fs = append(fs, func() { fmt.Println(v) }) }
이야기
다이내믹 라우팅 프로젝트에서는 여러 핸들러를 클로저를 통해 생성하기 위한 반복문을 사용했으며, 각각은 자신의 경로를 캡쳐해야 했습니다. 그 결과 모든 핸들러가 마지막 경로를 출력했습니다 — 각 클로저에 별도의 변수를 만들지 않았습니다. 오류는 HTTP API와의 통합 중에 발견되었습니다.
이야기
루프 안에서 고루틴을 통해 병렬 접근을 테스트할 때 클로저가 인덱스의 참조를 캡쳐했습니다, 즉 복사가 아닌. 이로 인해 "우연한" 효과가 발생했습니다: 데이터가 자신의 배열 슬롯이 아니라 마지막 슬롯에 기록되었습니다.
이야기
통계 수집 함수에서는 클로저가 외부의 공용 변수를 변경했으나, 작성자는 각 작업에 대해 독립적인 카운터를 기대했습니다. 문제는 항상 공용인 비정상적으로 재구성되는 합계로 발견되었습니다, 이는 로컬 논리에도 불구하고 개인적이지 않았습니다.