Go/nil
nil
はGoの事前宣言された識別子(predeclared identifier)で、以下の型のゼロ値を表します:
- ポインタ型
- 関数型
- インターフェース型
- スライス型
- マップ型
- チャネル型
各型でのnilの使用例
1. ポインタ型
var p *int if p == nil { fmt.Println("ポインタはnilです") } // nilポインタの逆参照はpanicを引き起こす // fmt.Println(*p) // panic: runtime error
2. 関数型
var f func() if f == nil { fmt.Println("関数はnilです") } // nil関数の呼び出しはpanicを引き起こす // f() // panic: runtime error
3. インターフェース型
var i interface{} if i == nil { fmt.Println("インターフェースはnilです") } // typed nilの場合 var err error var p *int err = p // typed nil if err == nil { fmt.Println("これは実行されない") // errは非nilになる }
4. スライス型
var s []int if s == nil { fmt.Println("スライスはnilです") } // nilスライスでも一部の操作は可能 fmt.Println(len(s)) // 0 fmt.Println(cap(s)) // 0 // s = append(s, 1) // これは動作する
5. マップ型
var m map[string]int if m == nil { fmt.Println("マップはnilです") } // nilマップからの読み取りは可能(ゼロ値を返す) value := m["key"] // 0 // nilマップへの書き込みはpanicを引き起こす // m["key"] = 1 // panic: assignment to entry in nil map
6. チャネル型
var ch chan int if ch == nil { fmt.Println("チャネルはnilです") } // nilチャネルでの操作は永続的にブロック // <-ch // 永続的にブロック // ch <- 1 // 永続的にブロック
nilの比較と判定
等価比較
var p1, p2 *int fmt.Println(p1 == p2) // true(両方ともnil) fmt.Println(p1 == nil) // true var s1, s2 []int fmt.Println(s1 == nil) // true // fmt.Println(s1 == s2) // コンパイルエラー:スライス同士は比較不可
reflect.Valueまたはreflectパッケージでの判定
import "reflect" func isNil(i interface{}) bool { if i == nil { return true } v := reflect.ValueOf(i) switch v.Kind() { case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice: return v.IsNil() } return false }
nilとゼロ値の区別
// 構造体のゼロ値はnilではない type Person struct { Name string Age int } var p Person // ゼロ値:{Name: "", Age: 0} // p != nil // コンパイルエラー // ポインタのゼロ値はnil var ptr *Person // nil
nilの実用的なパターン
1. オプショナル引数
func processData(data []int, callback func(int)) { for _, item := range data { if callback != nil { callback(item) } } }
2. 遅延初期化
type Cache struct { data map[string]interface{} } func (c *Cache) Get(key string) interface{} { if c.data == nil { c.data = make(map[string]interface{}) } return c.data[key] }
3. エラーハンドリング
func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil // エラーがない場合はnilを返す }
4. リソースのクリーンアップ
type Resource struct { file *os.File } func (r *Resource) Close() error { if r.file != nil { err := r.file.Close() r.file = nil // 重複クローズを防ぐ return err } return nil }
nilのイディオム
Guard句でのnil チェック
func process(data *Data) error { if data == nil { return errors.New("data cannot be nil") } // 処理続行 return nil }
nil interface{}の判定
func isEmpty(v interface{}) bool { return v == nil || reflect.ValueOf(v).IsNil() }
nil チャネルの活用
// selectでnilチャネルを無効化 func worker(stop <-chan struct{}) { var input <-chan Work for { select { case work := <-input: // inputがnilなら永続的にブロック(この分岐は実行されない) processWork(work) case <-stop: input = nil // チャネルを無効化 return } } }
注意点
- typed nil: インターフェースに代入されたnilポインタは
nil
と等価比較できない - nilポインタの逆参照: panic を引き起こす
- nilマップへの書き込み: panic を引き起こす
- nilチャネルの操作: 永続的にブロック
nilは Goにおける重要な概念で、適切に理解して使用することで、安全で効率的なコードを書くことができます。
カテゴリ:Go