コンテンツにスキップ

Goのインターフェース値の内部構造とnilバグの罠

概要

Go のインターフェース値は内部的に (type, value) の2つのポインタで構成されている。この仕組みを理解していないと、「nil を代入したはずなのに nil でない」という直感に反するバグを生み出す。Go を書くなら必須の知識。

詳細

インターフェース値の内部構造

interface{}
┌─────────────┬─────────────┐
│  type ptr   │  value ptr  │
│  (*T)       │  (*v)       │
└─────────────┴─────────────┘

インターフェース値が nil になるのは、type と value が両方 nil のとき だけ。

nil interface vs interface containing nil

package main

import "fmt"

type MyError struct {
    msg string
}

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

// BAD: *MyError の nil を error インターフェースに包んで返す
func badFunction() error {
    var err *MyError = nil
    return err // type=*MyError, value=nil → インターフェースとしては nil ではない!
}

// GOOD: nil を直接返す
func goodFunction() error {
    return nil // type=nil, value=nil → インターフェースとして nil
}

func main() {
    err1 := badFunction()
    fmt.Println(err1 == nil)  // false ← 罠!
    fmt.Println(err1)          // <nil>  ← 表示は <nil> なのに...

    err2 := goodFunction()
    fmt.Println(err2 == nil)  // true
}

よくあるバグパターン

// 条件付きで具体型の nil を返してしまうパターン
func process(input string) error {
    var customErr *ValidationError

    if input == "" {
        customErr = &ValidationError{msg: "empty input"}
    }

    if customErr != nil {
        return customErr
    }

    // ここで return nil とすべきだが...
    return customErr // input が空でない場合、*ValidationError(nil) を返す → バグ!
}

正しい判定方法

import "reflect"

// インターフェースが「実質的に nil」かどうかを安全に判定
func isNil(i interface{}) bool {
    if i == nil {
        return true
    }
    v := reflect.ValueOf(i)
    switch v.Kind() {
    case reflect.Ptr, reflect.Interface, reflect.Slice,
         reflect.Map, reflect.Chan, reflect.Func:
        return v.IsNil()
    }
    return false
}

まとめ表

状態 type value == nil
var e error nil nil true
var p *MyError; var e error = p *MyError nil false
e = &MyError{} *MyError 0x... false

なぜ重要か / いつ使うか

  • エラーハンドリングで具体型のポインタを返す関数を書くとき、必ず return nil と書くべき理由がわかる
  • if err != nil チェックが期待通り動かないバグの原因調査に直結する
  • Go の面接でよく問われる内部実装の知識として重要
  • インターフェースを多用するライブラリ設計(特にモック・テスト)で安全なコードを書くために必須