Go에서 for 루프 구조는 초기화 블록(init), 조건 검사, 후치 표현식이 포함될 수 있습니다. 역사적으로 이러한 메커니즘은 코드 작성을 용이하게 하고 C 유사 언어의 관습을 따르기 위해 만들어졌습니다. 그러나 Go에서는 루프 변수(i)의 범위 특성이 중첩 함수, 클로저 및 고루틴 내에서의 동작에 큰 영향을 미칩니다.
문제 — 고루틴이나 클로저를 각 반복에서 실행할 때 의도치 않은 동작이 발생하는 경우가 많습니다: 변수 i는 복사되지 않고 "참조로 캡처"되며, 즉 클로저가 루프의 공통 변수에 접근하게 되어 기댓값과 다르게 마지막 반복의 값을 가지게 됩니다. 이는 모든 고루틴/클로저에서 동일한 결과를 초래하지만, 로직은 그렇지 않을 것이라고 가정할 수 있습니다.
해결책 — 각 반복의 변수를 전달할 필요가 있을 경우, 명시적으로 변수를 복사(추가 변수를 통해)하거나 클로저의 인수로 전달하세요.
코드 예시:
for i := 0; i < 3; i++ { go func(j int) { fmt.Println(j) }(i) // 올바름! 복사된 값 } for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() // 오류: 모든 고루틴이 3을 출력함 }
주요 특징:
break 또는 continue 사용 시 for 변수의 범위가 변경되나요?
아니요. for에서 선언된 변수의 범위는 항상 해당 루프 블록으로 제한됩니다. break 또는 continue는 단지 현재 반복을 중단하지만 변수를 밖으로 "전파"하지 않습니다.
for의 init 부분에 선언된 변수를 루프 외부의 메서드에서 캡처할 수 있나요?
아니요. 변수는 오직 for 내부와 해당 블록의 모든 중첩에서만 보이며 루프가 끝난 후에는 그 외부에서 보이지 않습니다.
if 변수를 defer 표현식에서 캡처하면 어떻게 되나요?
상황은 동일합니다: defer 함수는 생성 시점의 값이 아니라 defer 실행 시점의 현재 변수 값을 "보게 됩니다" (보통 마지막 루프 값).
for i := 0; i < 3; i++ { defer fmt.Println(i) // 모든 defer가 3을 출력함 }
Go 웹 서버에서 개발자는 여러 고루틴을 다양한 포트를 처리하기 위해 실행했으며 포트 인덱스를 루프 변수로 사용하고 람다 표현식에서 이를 캡처했습니다. 모든 고루틴은 배열의 마지막 포트를 참조했습니다.
장점:
단점:
팀 내에서는 루프 변수의 값을 항상 새로운 변수에 복사하여 클로저/고루틴이 이를 캡처하도록 하는 규칙을 도입했습니다.
장점:
단점: