defer

Go의 defer 키워드에 대한 설명을 정리합니다.

아래 문서를 참고해서 작성했습니다.

defer

Go를 처음 접하면서 헷갈린 부분 중 하나가 예외 처리 방식이었습니다. 일반적으로 Java를 비롯한 객체지향 언어들과 최근 Javascript에서도 명시적으로 예외를 생성해 이를 throw 등의 키워드로 던지고, 이를 처리할 수 있는 코드 블럭에서 catch해 처리하는 방식에 익숙해져 있었는데요. Go에서는 이런 예외 상황들을 어떻게 처리하는지 공부하면서, 먼저 알아둬야 할 defer 키워드에 대해 정리합니다.

defer 키워드

Go의 예외 처리 방식인 panic과 recover를 이해하기 위해 필요한 키워드이기 때문에, 먼저 defer 키워드에 대해 정리해보려고 합니다.

defer의 사전적 의미는 “미루다, 지연하다” 입니다.

defer 키워드는 함수 호출을 리스트에 추가합니다. defer 구문이 속한 함수가 반환한 다음 이 리스트에 있는 함수를 최근에 추가된 순서로 호출합니다.

예를 들어, 파일을 읽기 위해 열고 난 뒤에는 Close() 함수를 호출해 리소스를 반환해야 합니다.

주로 할당받은 리소스를 명시적으로 해제해야 하는 등의 정리 작업에 유용하게 활용할 수 있습니다.

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

위의 코드에서 파일을 열거나 생성하는 도중 에러가 발생하면 함수를 반환하기 때문에 Close()는 결국 호출되지 않습니다. 코드가 복잡할 수록 이런 문제를 찾아 해결하기는 더욱 어려워지겠죠.

이런 경우에 defer를 활용하면 파일 리소스가 항상 해제됨을 보장할 수 있습니다:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

defer 구문은 크게 세 가지 단순한 규칙을 갖습니다:

  1. defer된 함수의 인자는 defer 구문을 평가할 때 평가된다.

즉, defer하는 호출에 전달하는 인자 값은 실제로 함수를 호출할 때가 아닌 defer 구문을 실행할 때의 값으로 평가됩니다.

   func main() {
   	i := 0
   	defer println(i)
   	for i < 4 {
   		i++
   	}
   }
   // Output:
   // 0
  1. defer된 함수 호출은 이를 감싸는 함수가 반환한 다음 Last In First Out 순서로 실행된다:

나중에 defer한 함수 호출이 가장 먼저 실행됩니다.

   func main() {
   	defer println("World")
   	defer println("Hello")
   	println("Say")
   }
   // Output:
   // Say
   // Hello
   // World
  1. defer된 함수는 반환하는 함수의 명명된 반환 값을 읽거나 할당할 수 있다.

Go는 함수 선언에서 반환 타입에 반환할 변수명을 함께 명시하면서 선언할 수 있는데요, defer한 함수를 호출할 때 이 명명된 반환값에 접근할 수 있습니다.

   func plus(a, b int) (result int) {
   	defer func() {
   		result = a + b
   	}()
   	result = 0
   	return
   }
   
   func main() {
   	println(plus(1, 2))
   }
   // Output:
   // 3
목록으로