프로그래밍시니어 Go 개발자

Go에서 클로저(함수 리터럴/클로저)를 어떻게 구현하고, 클로저 사용의 제약 조건과 특징은 무엇인지, 클로저가 저장되는 위치, 변수가 어떻게 캡쳐되는지, 다양한 시나리오에서 캡쳐된 변수의 동작 차이점은 무엇인지 설명해 주세요.

Hintsage AI 어시스턴트로 면접 통과

답변

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와의 통합 중에 발견되었습니다.


이야기

루프 안에서 고루틴을 통해 병렬 접근을 테스트할 때 클로저가 인덱스의 참조를 캡쳐했습니다, 즉 복사가 아닌. 이로 인해 "우연한" 효과가 발생했습니다: 데이터가 자신의 배열 슬롯이 아니라 마지막 슬롯에 기록되었습니다.


이야기

통계 수집 함수에서는 클로저가 외부의 공용 변수를 변경했으나, 작성자는 각 작업에 대해 독립적인 카운터를 기대했습니다. 문제는 항상 공용인 비정상적으로 재구성되는 합계로 발견되었습니다, 이는 로컬 논리에도 불구하고 개인적이지 않았습니다.