概要
go初学者です。
assignment to entry in nil mapで怒られる原因と、空のsliceへの値の追加との差を調査しました。
疑問点
下の場合、なぜs(slise)には要素を追加できて、m(map)には追加できない?
package main
import "fmt"
func main() {
var s []int
fmt.Println(s)
s = append(s, 1)
fmt.Println(s)
var m map[string]int
fmt.Println(m)
m["first"] = 100
fmt.Println(m)
}
出力
[]
[1]
map[]
panic: assignment to entry in nil map
原因
nilマップに要素を代入しようとしたから。
Assigning to an element of a nil map causes a run-time panic.
The Go Programming Language Specification
なぜnilマップには要素を追加できないのか
これは成功する・・・
package main
import "fmt"
func main() {
var s []int
fmt.Println(s)
s = append(s, 1)
fmt.Println(s)
// これはassignment to entry in nil map
// var m map[string]int
// fmt.Println(m)
// m["first"] = 100
// fmt.Println(m)
var m2 map[string]int
fmt.Println(m2)
m2 = make(map[string]int)
m2["first"] = 100
fmt.Println(m2)
}
出力
[]
[1]
map[]
map[first:100]
つまり、初期化しているか否かで結果が変わる。
では初期化の有無で何が異なるのでしょうか?
var m map[string]int //要素を追加できない
var m2 map[string]int = make(map[string]int) //要素を追加できる
package main
import "fmt"
func main() {
var s []int
fmt.Printf("%p %T %v\n", s, s, s)
var m map[string]int
fmt.Printf("%p %T %v\n", m, m, m)
var m2 map[string]int = make(map[string]int)
fmt.Printf("%p %T %v", m2, m2, m2)
}
出力
0x0 []int []
0x0 map[string]int map[]
0xc0000981b0 map[string]int map[]
ここで、一番左側に16進数で出力されているのはポインタの値(アドレス値)です。
※アドレス値とは変数がメモリ上のどこに保持されているのかを指す値。
したがって、m2変数以外はメモリ上に保持のための場所が確保されていないことが分かります。
つまり、初期化していないマップmに要素を追加しようとして「assignment to entry in nil map」が発生する原因は、メモリ上に要素追加に必要な場所が確保されていないからです。
ではどうすればよい?
下記のようにmake()で初期化し、場所を確保しましょう。
var m2 map[string]int = make(map[string]int)
新しい空のマップ値を作るには、組み込み関数makeを用いる。makeは、マップの型とオプションの容量ヒントを引数にとる。
マップは、nilマップを除いて、格納されているアイテムの数に合わせて大きくなる。nilマップは、要素が追加されないことを除けば、空のマップと同じである。
The Go Programming Language Specification
スライスに要素が追加できた理由
append()で要素を追加する場合、スライスに十分な容量がない場合は、新しい基になる配列が割り当てられるからです。
下記のようにlen()で初期化していないスライスの容量は0であることが分かります。
一方append()後はアドレス値が付与されて容量が1になっています。
したがって、この場合はマップの初期化後と同様に、メモリ上に場所が確保されています。
var s []int
fmt.Printf("%p %T %v\n", s, s, s)
fmt.Println(len(s))
s = append(s, 1)
fmt.Println("append後")
fmt.Printf("%p %T %v\n", s, s, s)
fmt.Println(len(s))
0x0 []int []
0
append後
0xc0000160b8 []int [1]
1
append組み込み関数は、スライスの最後に要素を追加します。十分な容量がある場合は、新しい要素に対応するために宛先が再スライスされます。そうでない場合は、新しい基になる配列が割り当てられます。Appendは、更新されたスライスを返します。したがって、多くの場合、スライス自体を保持する変数に、追加の結果を格納する必要があります。
The Go Programming Language Specification
まとめ
- nilマップには要素を追加できない
- その理由はメモリ上に要素追加に必要な場所が確保されていないから
- スライスに要素を追加できたのは、append()によって新しい基になる配列が割り当てられたから