Go のマップの存在チェック(コンマOK イディオム)
マップから値を取得するとき、そのキーが存在するかどうかを確認したい場面がよくある。Go では「コンマOK イディオム」を使ってこれを実現する。
存在しないキーの問題
マップに存在しないキーでアクセスすると、値の型のゼロ値が返る。
ages := map[string]int{
"Alice": 30,
}
fmt.Println(ages["Bob"]) // 0これだと「Bob の年齢が 0」なのか「Bob が存在しない」のか区別できない。
コンマOK イディオム
マップの値を取得するとき、2つ目の戻り値でキーの存在を確認できる。
ages := map[string]int{
"Alice": 30,
"Bob": 0,
}
age, ok := ages["Bob"]
fmt.Println(age, ok) // 0 true
age, ok = ages["Charlie"]
fmt.Println(age, ok) // 0 falseok が true ならキーが存在し、false なら存在しない。これで「値が 0」と「キーがない」を区別できる。
条件分岐での使い方
よくあるパターンとして、if 文と組み合わせて使う。
if age, ok := ages["Alice"]; ok {
fmt.Printf("Alice is %d years old\n", age)
} else {
fmt.Println("Alice not found")
}この書き方だと age 変数のスコープが if ブロック内に限定されるため、コードがすっきりする。
存在確認だけしたい場合
値が不要で存在確認だけしたいときは、ブランク識別子を使う。
if _, ok := ages["Alice"]; ok {
fmt.Println("Alice exists")
}実践的な例
ユーザーのキャッシュを管理する例を見てみよう。
type User struct {
ID int
Name string
}
var userCache = make(map[int]*User)
func GetUser(id int) (*User, error) {
// キャッシュを確認
if user, ok := userCache[id]; ok {
return user, nil
}
// キャッシュになければDBから取得
user, err := fetchUserFromDB(id)
if err != nil {
return nil, err
}
// キャッシュに保存
userCache[id] = user
return user, nil
}値がゼロ値かどうかの判定
コンマOK イディオムを使わないと、意図しない動作になることがある。
コンマOK なし
値がゼロ値のとき、キーが存在するのかどうか判断できない。
コンマOK あり
キーの存在を明確に判定できる。ゼロ値が有効な値として使われる場合に必須。
counts := map[string]int{
"apple": 5,
"banana": 0, // 在庫ゼロだが存在する
}
// 悪い例
if counts["banana"] == 0 {
fmt.Println("banana not found or out of stock")
}
// 良い例
if count, ok := counts["banana"]; ok {
if count == 0 {
fmt.Println("banana is out of stock")
}
} else {
fmt.Println("banana not found")
}コンマOK イディオムは Go のマップを扱う上で必須の知識だ。キーの存在を確認するときは常にこの方法を使うようにしよう。