LoginSignup
4
1

More than 1 year has passed since last update.

【Golang】assignment to entry in nil mapで怒られる原因

Last updated at Posted at 2022-05-01

概要

go初学者です。
assignment to entry in nil mapで怒られる原因と、空のsliceへの値の追加との差を調査しました。

疑問点

下の場合、なぜs(slise)には要素を追加できて、m(map)には追加できない?

main.go
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マップには要素を追加できないのか

これは成功する・・・

main.go
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) //要素を追加できる
main.go
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()によって新しい基になる配列が割り当てられたから
4
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1