テック技報最新号へ →
コード

Go: nil ポインタを入れた error は nil ではない(成功時は return nil と書く)

error などのインターフェース値は「型」と「値」の組。*MyErr 型の nil ポインタを error として返すと、型の情報が残るので err != nil が true になる。

@amitsinha__07 · 投稿 2026-10-13
#go#error#interface#nil#落とし穴

何が起きるか

go
type MyErr struct{ msg string }

func (e *MyErr) Error() string { return e.msg }

func load() error {
    var e *MyErr // nil ポインタ
    // ... 失敗したときだけ e = &MyErr{...} にするつもり
    return e // ← 成功時もここを通ると「(*MyErr, nil)」という error になる
}

func main() {
    if err := load(); err != nil {
        fmt.Println("失敗扱いになる:", err) // 成功のはずなのにここに入る
    }
}

インターフェース値が nil と等しいのは「型も値も空」のときだけ。return e で *MyErr という型が入るため、中身が nil でも != nil になる。呼び出し側で err.Error() を呼ぶと、メソッドによっては nil ポインタ参照で panic する。

直し方

  • 成功時は型付きの変数ではなく、リテラルの nil を返す。
    go
    func load() error {
        if bad {
            return &MyErr{msg: "bad"}
        }
        return nil
    }
  • 関数の戻り値の型は具体型(*MyErr)ではなく error にする。具体型で返して呼び出し側で error 変数に代入すると、同じ問題が起きる。
  • 途中でどうしても具体型の変数を使うなら、返す直前に if e == nil { return nil } と分岐する。

見つけ方

  • staticcheck の SA4023 は「インターフェース値と nil の比較が常に true / false になる」ケースを指摘してくれる(すべての経路を見つけられるわけではない)。
  • 同じ落とし穴は error 以外のインターフェース(io.Reader など)でも起きる。

出典

X の元の投稿を開く