新しいことにはウェルカム

技術 | 電子工作 | ガジェット | ゲーム のメモ書き

Go言語でハマったことメモ(マップに構造体を入れると「cannot assign to struct field ...」のエラーになる)

Golangを始めました。

始めてみて感じたのですが、Golangはできるだけ文法をシンプルにかつ、できるだけコードがシンプルになるように設計されています。

そしてそのために、Golangには若干トリッキーな構文がいくつかあります。

しかし、それらを知らずに、他の言語での先入観や勝手な思い込みで判断してしまって、ハマることがちょいちょいありました。

ここでは、Golangを始めてみて、個人的にハマったことや、勘違いしたことを、トピック別に備忘録としてメモしていこうと思います。

今回は、マップに構造体を入れると「cannot assign to struct field ...」のエラーになることについてのメモです。

マップに格納した構造体の要素に代入はできない

マップには構造体を格納できるのですが、その構造体の要素に代入(=)しようとすると

cannot assign to struct field ...

というエラーが出てできません。

コードにすると下記のような感じです。

package main

type myStruct struct {
    x int
}

func main(){
    a := make(map[int]myStruct)
    a[0] = myStruct{x: 0}

    // コンパイルエラーでできない
    a[0].x = 1  // cannot assign to struct field a[0].x in map 
}

これは困った…。

他のケースを見てみます。

マップに格納した構造体への代入はできる

当然ですが、マップの要素に対しては構造体全体で代入できます。

package main

type myStruct struct {
    x int
}

func main(){
    a := make(map[int]myStruct)
    a[0] = myStruct{x: 0}

    // これはできる
    a[0] = myStruct[x: 1}
}

マップに格納した構造体の要素の値は取得できる

構造体の要素の値の取り出しは問題ないです。

package main

type myStruct struct {
    x int
}

func main(){
    a := make(map[int]myStruct)
    a[0] = myStruct{x: 0}

    // これはできる
    if 0 == a[0].x {
        fmt.Println(a[0].x)  // 0
    }
}

マップに格納した構造体のメソッドの呼び出し

メソッドは、オブジェクトに対しては呼べるのですが、ポインターに対してはコンパイルエラーになります。

package main

type myStruct struct {
    x int
}

func (o myStruct) object(){
    fmt.Println(o.x)
}

func (p *myStruct) pointer(){
    fmt.Println(p.x)
}

func main(){
    a := make(map[int]myStruct)
    a[0] = myStruct{x: 0}

    // これはできる
    a[0].object()  // 0

    // これはコンパイルエラーでできない
    a[0].pointer()  // cannot take the address of a[0]
}

これは、オブジェクトの場合は、関数に渡す際に、マップに格納した構造体をコピーして、その構造体を操作するのに対し、ポインターの場合は、マップに格納した構造体のアドレスを介して、その構造体を直接操作しようとする動作の違いに起因しています。

原因と対処方法

ポインターのメソッドを呼び出した時のエラー「cannot take the address」にあるように、マップに格納した要素(構造体)を取り出した時、それは格納した要素(構造)の実体ではありません。

なので、取り出した要素の値は取得できるのですが、要素の内部を変更したり操作することはできないようになっています。

要素全体を「=」演算子で新しいものと入れ替えたり、「delete()」関数で、要素そのものを削除することはできます。

これらの挙動は、Golangの仕様なので受け入れるしかなく、前述の例のように、構造体の値を変更したい場合は、構造体ごと新しいものに入れ替えるしかありません。

ただ、構造体が巨大だと、毎回構造体をコピーして入れ替えるのはコストが高いので、マップには構造体そのものを格納するのではなく、構造体のポインターを格納するようして対処するのがいいかなと思います。

コードにすると下記のような感じです。

package main

// 巨大な構造体
type myStruct struct {
    x     int
    array [10000]int
}

func main(){
    // 構造体の実体ではくポインターを格納する
    a := make(map[int]*myStruct)
    a[0] = &myStruct{x: 0}

    // これはできる
    a[0].x = 1
    fmt.Println(a[0].x)  // 1
}

感想など

Golangはシンプルだと聞いて、何となく雰囲気でプログラミングできるのかなと軽い気持ちで始めたのですが、実体かポインターかは常に意識させられますね。

参考記事