defer 문

defer 문

defer 문은 함수의 실행을 미루는 문법이에요. 함수를 호출하긴 하는데, 그 실행 시점을 현재 함수가 반환되는 바로 그 순간으로 뒤로 넘겨요. 어떤 일을 잊어버리지 않고 확실하게 처리하고 싶을 때 — 가령 자원을 열었으면 반드시 닫아야 할 때 — 그 '마무리 작업'을 미리 등록해 두는 용도로 아주 많이 쓰죠.

출처: Go Specification

본문

defer 문은 둘러싼 함수가 반환하는 순간까지 그 실행을 미루는 함수를 호출해요. '반환하는 순간'이 언제인지는 상황에 따라 조금씩 달라질 수 있어요. 둘러싼 함수가 return 문을 실행했을 수도 있고, 함수 본문의 끝까지 도달했을 수도 있으며, 해당 고루틴이 패닉을 일으키고 있을 수도 있죠.

문법은 이렇게 생겼어요.

<a id="DeferStmt">DeferStmt</a> = "defer" <a href="#Expression" class="noline">Expression</a> .

한 가지 중요한 제약이 있어요. defer 뒤의 표현식함수나 메서드 호출이어야 하며, 괄호로 감싸면 안 돼요. 내장 함수의 호출은 표현식 문과 마찬가지로 제한이 적용돼요.

이제 실행 흐름을 자세히 볼게요. defer 문이 실행될 때마다, 그 호출의 함수 값과 인자들은 평소처럼 평가되고 새로 저장되지만, 실제 함수는 아직 호출되지 않아요. 대신 deferred 함수들은 둘러싼 함수가 반환하기 직전에, defer된 순서의 역순으로 호출돼요. 즉, 둘러싼 함수가 명시적 return 문으로 반환한다면, deferred 함수들은 그 return 문이 결과 인자들을 설정한 다음, 함수가 호출자에게 반환되기 전에 실행돼요. 여기서 재미있는 게 하나 있어요. deferred 함수 값이 nil로 평가된다면, 패닉은 defer 문이 실행될 때가 아니라 그 함수가 실제로 호출될 때 발생해요.

지연된 함수가 함수 리터럴이고, 둘러싼 함수가 이 리터럴 안에서 유효 범위에 있는 이름 있는 결과 인자를 가진다면, 그 deferred 함수는 결과 인자가 반환되기 전에 그 값을 읽거나 수정할 수 있어요. 그리고 deferred 함수가 반환 값을 가진다면, 그 값은 함수가 끝날 때 버려져요. (관련해서는 패닉 처리 섹션도 함께 보면 좋아요.)

실제 코드로 확인해 볼게요.

lock(l)
defer unlock(l)  // unlocking happens before surrounding function returns

// prints 3 2 1 0 before surrounding function returns
for i := 0; i &lt;= 3; i++ {
	defer fmt.Print(i)
}

// f returns 42
func f() (result int) {
	defer func() {
		// result is accessed after it was set to 6 by the return statement
		result *= 7
	}()
	return 6
}

가장 흔한 패턴은 위의 lock/defer unlock처럼 **'열었으면 닫는다'**는 짝을 지어 등록하는 거예요. 두 번째 예는 역순 실행을 보여주죠. 반복문 안에서 defer fmt.Print(i)를 0부터 3까지 등록했는데, 실행은 역순이라 3 2 1 0이 출력돼요. 마지막 예가 조금 까다로운데요. freturn 6으로 결과 인자 result를 6으로 설정하고, 그 다음에 deferred 함수가 result *= 7을 실행해서 42를 만들어 반환해요. 지연된 함수가 반환되기 전에 결과 인자를 손대는 걸 정확히 보여주는 사례죠.

더 알아보기