アルパカログ

Webエンジニア兼マネージャーがプログラミングやマネジメント、読んだ本のまとめを中心に書いてます。

Go言語 スライスの初期化と要素追加の注意点

f:id:otoyo0122:20200815104816p:plain:w300

Go言語に限らずポインタが存在する言語では「ポインタなのか?値なのか?」ということに常に気を配る必要があります。

さらにスライスになると「中身がポインタなのか?」「容れ物がポインタなのか?」「どちらもポインタなのか?」というように、注意すべきことが増え、気を抜くと簡単にランタイムエラーになってしまいます。

このエントリでは、スライスとスライスのポインタで異なる初期化方法や要素の追加についてまとめます。

スライスの初期化

初期化では&*の使い分けに注意します。

type Person struct {
    Name      string
}
var persons1 []*Person    // Person(ポインタ)のスライス
persons1 = []*Person {{Name: "Tom"}, {Name:"John"}}

var persons2 *[]Person    // Person(値)のスライスのポインタ
persons2 = &[]Person {{Name: "Tom"}, {Name: "John"}}

var persons3 *[]*Person   // Person(ポインタ)のスライスのポインタ
persons3 = &[]*Person {{Name: "Tom"}, {Name: "John"}}

要素の追加

スライスの要素の追加はappendを使います。

var persons1 []*Person
persons1 = append(persons1, &Person{Name: "Tom"})

スライスのポインタの場合は注意が必要です。

下記の例はSEGVを引き起こします。なぜでしょうか?

// これは間違い
var persons2 *[]Person
*persons2 = append(*persons2, Person{Name: "Tom"}) // => SEGV

これはスライスとスライスのポインタで、初期化時のnilが指す対象が異なるためです。

  • スライス[]*Personnil → 要素がない
  • スライスのポインタ*[]Personnil → スライスへの参照がない

var persons1 []*Personは「要素がない」スライスを初期化しているのに対し、var persons2 *[]Person は「スライスへの参照がない」ポインタを初期化しているためです。

参照がnilなので*persons2のようにデリファレンスできず nil pointer dereference のランタイムエラーになってしまうというわけです。

要はスライスへの参照があれば良いので、appendの前にスライスへの参照を与えると動作します。

var persons2 *[]Person
persons2 = &[]Person{{Name: "John"}}
*persons2 = append(*persons2, Person{Name: "Tom"})

繰り返し要素を追加する

スライスとスライスのポインタに対し、繰り返し(iteration)を使って要素を追加する例です。

下記の例では*[]Personを繰り返して[]*Studentに要素を追加しています。

先に挙げたappendで追加していく方法と、前もって長さの決まった容れ物を作って(make)から代入する方法があります。

type Student struct {
  Name string
}

var persons *[]Person
persons = &[]Person {{Name: "Tom"}, {Name: "John"}}

var students []*Student

// make() して要素を確保してから代入する方法
students = make([]*Student, len(*persons))
for i, person := range *persons {
    students[i] = &Student{Name: person.Name}
}

// make() せずに append() しても良い
for _, person := range *persons {
    students = append(students, &Student{Name: person.Name})
}

以上です。

このエントリでは、スライスとスライスのポインタで異なる初期化方法や要素の追加についてまとめました。

参考になった方は、ぜひ「はてブ」やSNSでシェアしていただけると嬉しいです。

配列とスライスを復習する

Goの配列とスライスを復習したいという方はぜひ下記の記事をご覧になってください。

alpacat.hatenablog.com