Go runtime error: invalid memory address or nil pointer dereference の原因と解決方法

runtime error: invalid memory address or nil pointer dereference とは

Goで「runtime error: invalid memory address or nil pointer dereference」はランタイムパニックを引き起こす深刻なエラーです。nilのポインタを間接参照(dereference)した場合に発生します。Go開発で最も頻繁に遭遇するpanicの一つです。

このエラーの原因は「nilのポインタやインターフェースに対してアクセスしている」の一点です。変数が適切に初期化されているかを確認しましょう。

エラーの発生パターン

このエラーは主に以下のようなケースで発生します。

パターン1: 初期化されていないポインタの使用

type User struct {
    Name string
}

func main() {
    var user *User  // nilポインタ
    fmt.Println(user.Name)  // panic: nil pointer dereference
}

ポインタ変数userは宣言のみで初期化されていないため、値はnilです。nilポインタのフィールドにアクセスするとpanicが発生します。

// 修正例1: ポインタを初期化
func main() {
    user := &User{Name: "Alice"}
    fmt.Println(user.Name)
}

// 修正例2: nilチェックを行う
func main() {
    var user *User
    if user != nil {
        fmt.Println(user.Name)
    }
}

パターン2: マップの未初期化

func main() {
    var m map[string]int  // nilマップ
    m["key"] = 1  // panic: assignment to entry in nil map
}

マップはゼロ値がnilであり、nilマップへの書き込みはpanicを引き起こします。読み取りはpanicにならずゼロ値を返す点に注意してください。

// 修正: makeで初期化
func main() {
    m := make(map[string]int)
    m["key"] = 1
    fmt.Println(m["key"])  // 1
}

パターン3: エラーハンドリングの不備

func main() {
    file, err := os.Open("nonexistent.txt")
    // errを無視してfileを使用
    defer file.Close()  // panic: nil pointer dereference
}

os.Openがエラーを返した場合、filenilになります。errを確認せずにfileを使用するとpanicが発生します。

// 修正: エラーを必ず確認
func main() {
    file, err := os.Open("nonexistent.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()
    // fileを安全に使用
}
Goでは戻り値のerrorは必ず確認する慣習があります。err != nil のチェックを省略するとnil pointer dereferenceの原因になります。go vetやstaticcheckなどの静的解析ツールでエラーハンドリングの漏れを検出できます。

根本原因の特定方法

panicが発生するとスタックトレースが出力されます。スタックトレースの最上部に表示されるファイル名と行番号を確認し、その行でポインタやインターフェースがnilになっていないかを確認します。delveデバッガを使うとブレークポイントを設定してステップ実行が可能です。

// デバッグ例
func main() {
    var user *User
    fmt.Printf("user: %v, is nil: %t\n", user, user == nil)
    // user: <nil>, is nil: true

    // nilチェック後に安全にアクセス
    if user != nil {
        fmt.Println(user.Name)
    }
}

防止策とベストプラクティス

nil pointer dereferenceを防ぐには、ポインタやマップ、スライスを使用する前に必ず初期化を確認します。関数の戻り値でerrorが返される場合は必ずチェックし、nilの可能性がある値にはガード条件を入れましょう。

// 防止策まとめ
// 1. エラーチェックを必ず行う
result, err := someFunc()
if err != nil {
    return fmt.Errorf("someFunc failed: %w", err)
}

// 2. ポインタのnilチェック
if ptr != nil {
    fmt.Println(ptr.Field)
}

// 3. マップは必ずmakeで初期化
m := make(map[string]int)

// 4. コンストラクタ関数で初期化を保証
func NewUser(name string) *User {
    return &User{Name: name}
}
Goでは「エラーは値である」という思想に基づき、errの確認を徹底することがnil pointer dereference防止の最も重要なプラクティスです。

Stack Overflowでの質問状況

Stack Overflowでは、Goに関する質問が約75,050件投稿されており、runtime error: invalid memory address or nil pointer dereferenceは最も頻繁に質問されるエラーカテゴリの一つです。

よくある質問(FAQ)

Q
panicとerrorの違いは何ですか?
A

errorはGoの通常のエラーハンドリング手段であり、関数の戻り値として返されます。panicはプログラムの実行を中断する深刻なエラーで、recover()で回復可能ですが、通常のエラー処理にはerrorを使うのがGoの慣例です。

Q
nilマップの読み取りはpanicにならないのですか?
A

はい、nilマップからの読み取りはpanicにならず、値の型のゼロ値(intなら0、stringなら空文字列)を返します。ただしnilマップへの書き込みはpanicになります。この非対称性はGo初学者がつまずきやすいポイントです。

Q
go vetでnil pointer dereferenceを検出できますか?
A

go vetは一部のnil pointer dereferenceを静的に検出できますが、すべてのケースを検出することはできません。より高度な検出にはstaticcheck、nilaway(Uber製)などの外部ツールの併用が推奨されます。

免責事項: 当記事の情報は執筆時点の内容に基づいています。最新情報は各公式サイトをご確認ください。当サイトは情報提供を目的としており、資格取得・技術的対応の結果について一切の責任を負いません。

コメント

タイトルとURLをコピーしました