출처: https://zenn.dev/takuoki/articles/64e5702f0134f0

이전에 만들었던 코드에서의 panic 구현

원래 (v1.20까지) panic의 인수에 nil을 지정하면 recover에서 nil이 취득 되었다.

main.go

package main

import "fmt"

func main() {
   
defer func() {
       
if e := recover(); e != nil {
           fmt.Println(
"type: %[1]T, value: %[1]v\n", e)
           
return
       }
       fmt.Println(
"recover returns nil") // 이것이 출력된다
   }()

   
panic(nil)
}

이전에 만든 코드에서는 뭔가 panic가 발생했을 경우에 그것을 error로 변환하여 반환 하였다. Goroutine 실행 시나, API의 Middleware 등에서 이용되는 것을 상정하고 있었다.

통상은 nil을 인수로 panic 시키는 것은 없다고 생각하지만, 라이브러리이므로 이것도 지원할 수 있도록, 아래와 같은 (샘플 코드)를 쓰고 있었다 .

sample.go

func sample() {
   panicked :=
true
   
defer func() {
       
if r := recover(); r != nil || panicked {
           err = recovery.Recovery(r)
       }
   }()

   fmt.Println(
"your code")
   panicked =
false
   
return nil
}

panic 발생 유무를 확인하기 위한 변수를 panicked를 정의하고 판단하는 구현은 v1.4 무렵의 Go gRPC Middleware 를 참고로 하고 있었다.

테스트는 매우 간단했고, panic(nil) 을 recover 했을 때의 에러 메세지를 전방 일치로 assert 하고 있었다 ( 해당 위치 ).

v1.21의 동작

v1.21 에서 panic(nil) 했던 것을 recover 했을 경우 *runtime.PanicNilError 를 취득하게 되었다.

Go 1.21 now defines that if a goroutine is panicking and recover was called directly by a deferred function, the return value of recover is guaranteed not to be nil. To ensure this, calling panic with a nil interface value (or an untyped nil a run-time panic of type *runtime.PanicNilError.

Go 1.21에서는 goroutine이 패닉을 일으키고 recover가 defer 함수에서 직접 호출되면 recover의 반환 값이 nil이 아님을 보장하는 것으로 정의 되었다. 이를 보장하기 위해 nil 인터페이스 값 (또는 타입이 지정되지 않은 nil)으로 panic을 호출하면 * runtime.PanicNilError 타입의의 런타임 패닉이 발생한다.

그 결과, 정말로 panic이 발생했는지를 확인하는 불필요한 코드도 쓰지 않아서 좋아졌다. 깔끔하게.