🐹

いつの間にかにpanic(nil)の挙動が変わってた

2024/02/12に公開

最近、Goでプログラムを書く機会が減っていたのだが、久々にGoを触って、個人用のアプリケーション開発に使うライブラリのバージョンを上げようとしたらテストで怒られてしまった。

何で怒られたんだろうと確認してみたら、どうやらGoのv1.21から panic(nil) の挙動が変わっていることが原因の様子。後方互換性が基本的に保たれるGoで、バージョンを上げただけでテストが落ちたのが面白いので、備忘としてブログを書いてみる。

元々のライブラリの実装

元々(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) causes a run-time panic of type *runtime.PanicNilError.

Go 1.21では、goroutineがパニックを起こし、recoverがdefer関数から直接呼び出された場合、recoverの戻り値がnilでないことが保証されると定義されました。これを保証するために、nilインタフェース値(または型付けされていないnil)でpanicを呼び出すと、*runtime.PanicNilError型のランタイムパニックが発生します。

その結果、本当にpanicが発生したのかを確認する余計なコードも書かなくて良くなった。すっきり。


バージョンを上げただけでテストが落ちたけど、panicの引数にnilを指定するような人は基本的にいないはずなので、実影響がある人はそうそういないでしょう。むしろ、Middlewareでrecoverするように実装していたのに、不慣れな人がアプリケーション内部に panic(nil) を紛れ込ませてしまっても期待通りの挙動になるので、嬉しい人の方が多いはず。

Goはv1.22でも、ループの変数に関して互換性のない修正が入ったりしていますが、これもおそらく多くのGopherを悩ませていたGoのイケていない挙動のひとつだったので、この修正を心待ちにしていた人も多かったはず。バージョンアップ時には気をつけないといけませんが、言語が良い方向にアップデートされるのは嬉しいですね。

Discussion