
Golangに初めて触れた方は、Golangのstructによく悩まされます。初期化方法、様々なデータタイプと構造体の組み合わせ方は、これまでに経験した他の言語とはかなり異なります。本文ではこれらの点について整理していきます。
疑問¶
Go では struct を初期化する方法がいくつかあります:
- 初期化の値:foo := Foo{}、またはデフォルトの方法で作成することもできます var foo Foo
- 初期化をポインタにする:foo := New(Foo)、もちろん値として初期化してからアドレスを取得することもできます foo := &Foo{}
❓
では、これらの方法にはどのような違いがあるのでしょうか?なぜシンプルさを重視する Go が、これほど多くの異なる初期化方法を導入したのでしょうか?
Go には多くの型が存在し、以下を含みます:
- リテラル値/ポインタ:数値、文字列、ブール、配列
- 構造体の値/ポインター
- slice、map、channel(makeで初期化が必要な値)
- Goのstructには独特の組み合わせパターンがあり、それには内蔵値と内蔵ポインタの違いが含まれています。
以下のように Foobar 構造体を定義します:
type Foo struct {
}
func (Foo) callVFoo() {
fmt.Println("vfoo")
}
func (*Foo) callPFoo() {
fmt.Println("pfoo")
}
type Bar struct {
}
func (Bar) callVBar() {
fmt.Println("vbar")
}
func (*Bar) callPBar() {
fmt.Println("pbar")
}
type Foobar struct {
Foo
*Bar
vFoo Foo
pFoo *Foo
vNum int
pNum *int
vArr [5]int
pArr *[5]int
s []int
m map[int]int
c chan int
}
❓
これらのメンバ変数は、どのような情報で初期化されますか?
実験¶
異なる fb に対して以下のコードを実行し、結果を観察してください:
1. fb := Foobar{}
2. fb := new(Foobar)
3. var fb Foobar
4. var fb *Foobar
func test(fb) {
fmt.Printf("%+v\n", fb)
fmt.Printf("slice is nil: %t\n", fb.s == nil)
fmt.Printf("map is nil: %t\n", fb.m == nil)
fmt.Printf("chan is nil: %t\n", fb.c == nil)
fb.callPFoo()
fb.callVFoo()
fb.callPBar()
fb.callVBar()
}
运行结果1:
{Foo:{} Bar:<nil> vFoo:{} pFoo:<nil> vNum:0 pNum:<nil> vArr:[0 0 0 0 0] pArr:<nil> s:[] m:map[] c:<nil>}
slice is nil: true
map is nil: true
chan is nil: true
pfoo
vfoo
pbar
panic: runtime error: invalid memory address or nil pointer dereference
运行结果2:
&{Foo:{} Bar:<nil> vFoo:{} pFoo:<nil> vNum:0 pNum:<nil> vArr:[0 0 0 0 0] pArr:<nil> s:[] m:map[] c:<nil>}
slice is nil: true
map is nil: true
chan is nil: true
pfoo
vfoo
pbar
panic: runtime error: invalid memory address or nil pointer dereference
运行结果3:
{Foo:{} Bar:<nil> vFoo:{} pFoo:<nil> vNum:0 pNum:<nil> vArr:[0 0 0 0 0] pArr:<nil> s:[] m:map[] c:<nil>}
slice is nil: true
map is nil: true
chan is nil: true
pfoo
vfoo
pbar
panic: runtime error: invalid memory address or nil pointer dereference
运行结果4:
<nil>
panic: runtime error: invalid memory address or nil pointer dereference
结論¶
- 前の3つの初期化方法では、struct valueまたはstruct pointerを取得するかもしれませんが、最終的に指し示す値のメモリは同じです。ただし、*structを直接宣言した場合は、初期化されずにnilを取得します。
- 内部構造体が値として埋め込まれている場合は初期化できますが、ポインタとして埋め込まれている場合は初期化できません。
理由:内部構造体を埋め込んだ場合、外部構造体を初期化する際に一括して連続したメモリが割り当てられ、同時に内部構造体も初期化されます。しかし、ポインタを埋め込んだ場合はそうではありません。
また、埋め込み構造体が nil であっても、レシーバがポインタの func を呼び出すことはできますが、レシーバが値の func を呼び出すことはできません。
- slice、map、channel など make が必要なタイプは、自動的に初期化されません。これら3つのタイプは本質的に参照型であり、未知のサイズのメモリスペースを参照する必要があるため、直接初期化されないのです。