🔥 defer와 함수 호출 스택

343자
5분

Go 언어에서 defer 키워드를 사용하면 함수나 메서드의 실행을 지연시킬 수 있어요. 지연된 함수 호출은 스택(stack)에 쌓이게 되죠. 그리고 현재 함수가 리턴할 때, 스택에 쌓인 지연 호출들이 LIFO(Last-In-First-Out) 순서로 실행된답니다.

defer에 대해 더 자세히 알고 싶다면 이 블로그 포스트를 읽어보세요.

자, 그럼 예제 코드를 통해 defer가 어떻게 동작하는지 알아볼까요?

go
package main
 
import "fmt"
 
func main() {
	fmt.Println("카운팅 시작") // 가장 먼저 출력됩니다.
 
	for i := 0; i < 10; i++ {
		defer fmt.Println(i) // 루프를 돌면서 지연 호출을 스택에 쌓습니다.
	}
 
	fmt.Println("끝!") // 카운팅이 끝난 후 출력됩니다.
}
 
go
package main
 
import "fmt"
 
func main() {
	fmt.Println("카운팅 시작") // 가장 먼저 출력됩니다.
 
	for i := 0; i < 10; i++ {
		defer fmt.Println(i) // 루프를 돌면서 지연 호출을 스택에 쌓습니다.
	}
 
	fmt.Println("끝!") // 카운팅이 끝난 후 출력됩니다.
}
 

위 코드를 실행하면 다음과 같은 출력 결과를 얻을 수 있어요.

text
카운팅 시작
끝!
9
8
7
6
5
4
3
2
1
0
text
카운팅 시작
끝!
9
8
7
6
5
4
3
2
1
0

코드의 실행 흐름을 단계별로 살펴보면:

  1. main() 함수가 실행되면서 가장 먼저 "카운팅 시작"이 출력돼요.
  2. 그 다음, for 루프가 시작되죠. 루프 변수 i는 0부터 9까지 증가하면서 반복합니다.
  3. 루프 내에서 defer fmt.Println(i)가 호출되는데, 이는 해당 출력문의 실행을 지연시키고 스택에 쌓아둡니다. 즉, 루프를 돌면서 defer로 지정된 출력문들이 차례로 스택에 쌓이게 되는 거예요.
  4. 루프가 끝나면 "끝!"이 출력됩니다. 아직 defer로 지정된 출력문들은 실행되지 않은 상태죠.
  5. main() 함수가 리턴하기 직전, 스택에 쌓여있던 지연 호출들이 LIFO 순서로 실행됩니다. 가장 마지막에 스택에 쌓인 defer fmt.Println(9)가 가장 먼저 실행되고, 그 다음은 defer fmt.Println(8), ... 이런 식으로 역순으로 실행되어 9부터 0까지 차례로 출력되는 거죠.

이렇게 defer를 사용하면 현재 함수가 리턴하기 직전에 실행되어야 할 작업들을 깔끔하게 정리할 수 있답니다. 자원 해제, 클린업 작업 등에 유용하게 사용할 수 있어요.

defer의 또 다른 장점은 에러 처리에 있어요. 에러가 발생했을 때 defer로 지정된 코드는 항상 실행이 보장되거든요. 이를 이용해 에러 발생 시 자원 해제나 롤백 등의 작업을 안전하게 수행할 수 있습니다.

go
func doSomething() error {
	file, err := os.Open("file.txt") // 파일을 엽니다.
	if err != nil {
		return err
	}
	defer file.Close() // 함수 종료 직전 파일을 닫습니다. 에러 발생해도 실행 보장!
 
	// 파일 처리 작업 수행...
	// ...
 
	return nil
}
 
go
func doSomething() error {
	file, err := os.Open("file.txt") // 파일을 엽니다.
	if err != nil {
		return err
	}
	defer file.Close() // 함수 종료 직전 파일을 닫습니다. 에러 발생해도 실행 보장!
 
	// 파일 처리 작업 수행...
	// ...
 
	return nil
}
 

이런 식으로 에러가 발생하더라도 defer로 지정된 file.Close()는 반드시 실행되므로, 열어둔 파일 핸들이 제대로 닫힘을 보장할 수 있는 거예요.

지금까지 defer에 대해 알아봤는데요. 함수 리턴 직전에 실행되어야 할 작업이 있다면 defer를 적극 활용해 보세요.

YouTube 영상

채널 보기
NestJS 커스텀 예외 만들기 - 에러 처리 깔끔하게 하는 법 | NestJS 가이드
함수형 미들웨어 | NestJS 가이드
NestJS 미들웨어 기초 - 클래스 기반 미들웨어와 DI | NestJS 가이드
커스텀 예외 필터 만들기 | NestJS 가이드
펑터란? | 프로그래머를 위한 카테고리 이론
Const 펑터 - 아무것도 안 하는 펑터가 필요한 이유 | 프로그래머를 위한 카테고리 이론
Product와 Coproduct가 Bifunctor인 이유 | 프로그래머를 위한 카테고리 이론
앨런 튜링이 들려주는 튜링 테스트와 보편 기계 이야기