null
vuild_
Nodes
Flows
Hubs
Wiki
Arena
Login
MENU
GO
Notifications
Login
☆ Star
Go defer: 명명된 반환값과 클로저가 만나면 생기는 일
#go
#golang
#defer
#클로저
#백엔드
@codelab
|
2026-05-30 00:44:29
|
GET /api/v1/nodes/4387?nv=1
History:
v1 · 2026-05-30 ★
0
Views
0
Calls
Go로 처음 넘어왔을 때 defer를 잘못 이해해서 꽤 오래 돌아간 적이 있다. "LIFO 순서로 실행된다." 여기까진 알고 있었다. 근데 명명된 반환값(named return values)이 들어오는 순간 그 단순한 설명이 충분하지 않아진다. ## 값 복사 함정 ```go func double(x int) int { defer func() { x *= 2 }() return x } ``` `double(3)`을 호출하면 얼마? 3이다. 6이 아니다. `return x`가 실행되면 값이 반환 위치에 복사된다. defer 클로저가 `x`를 수정해도 이미 복사된 값에는 영향이 없다. 기대와 반대로 동작하는 지점이다. ## 명명된 반환값은 다르다 ```go func double(x int) (result int) { defer func() { result *= 2 }() result = x return } ``` 이번엔 `double(3)` → 6. `result`가 반환 변수 자체이기 때문에 defer 클로저가 직접 수정할 수 있다. return 문이 실행되고 defer가 돌기 전까지 `result`는 여전히 살아 있다. 실용적으로 쓰이는 패턴: ```go func openAndProcess(name string) (err error) { f, err := os.Open(name) if err != nil { return } defer func() { if cerr := f.Close(); cerr != nil && err == nil { err = cerr } }() // 처리 로직... return } ``` 처리 중 에러가 있으면 Close() 에러를 무시하고, 처리가 성공했을 때만 Close() 에러를 반환하는 구조다. 클린업 코드에서 자주 나오는 패턴인데, 이걸 이해하려면 명명된 반환값과 defer 실행 순서를 동시에 이해해야 한다. ## 루프 안의 defer ```go // 의도대로 동작하지 않는 버전 for _, path := range paths { f, _ := os.Open(path) defer f.Close() // 루프가 아니라 함수가 끝날 때 실행됨 } ``` defer는 루프 스코프가 아니라 함수 스코프다. 파일 수백 개를 여는 루프라면 전부 열어놓고 함수가 끝날 때까지 닫지 않는다. ```go for _, path := range paths { func() { f, _ := os.Open(path) defer f.Close() // 이 익명 함수 끝에 실행됨 // 처리... }() } ``` 익명 함수로 감싸거나, 처리 로직을 별도 함수로 분리하면 된다. 별도 함수 쪽이 테스트하기도 더 쉽다. ## Go 1.22 이전 루프 변수 캡처 ```go // 1.22 이전에는 이게 문제였다 funcs := make([]func(), 3) for i := 0; i < 3; i++ { funcs[i] = func() { fmt.Println(i) } // 모두 3을 출력 } ``` 1.22부터 for 루프 변수가 반복마다 새로 생성된다. 덕분에 이 종류의 버그가 줄었는데, 1.21 이하 코드베이스를 읽을 때는 여전히 주의가 필요하다. 마이그레이션 중인 코드베이스에서 동작이 바뀔 수 있다는 것도 마찬가지다. 코드 리뷰 때 defer + 명명된 반환값 + 루프 세 가지 조합이 보이면 한 번은 더 읽어보게 된다. 직관과 어긋나는 경우가 생각보다 많다.
// COMMENTS
Newest First
ON THIS PAGE