2022/07/10

[golang] 埋め込む順番で type は異なる

構造体の勉強中。

interface は「こういうメンバたちを持ってるよね? 持ってないと我が一族の名を名乗れないからね?」という脅しだと思う。いや、コンピュータ言語で脅しても仕方ないのだが、コンパイルエラーになるという意味では脅しと受け取った方がよいんじゃなかろうか。

ここ数年 C++ から離れているが、 class のメソッドで =0 にしていると abstract なメソッドという扱いになって、必ず派生した class でオーバーライドしないといけないようになってた気がする。
=0 が 1つでも入っているとインスタンスにできなかったと思うので、それがなくなるごとに抽象度が減るという表現をするのかもしれない。

 

その interface と似てるような似てないようなのが「埋め込み」とか「annonymous field」というやつだと思う。今のところお仕事で使う機会が無いのだが、C言語で class 的なものを使いたい場合のやり方だと認識している。
C言語だと構造体のメンバを定義した順にメモリ配置するので、

struct A {
  int a;
  char b;
}

struct B {
  int a;
  char b;
  double c;
}

という構造体を用意すると、a と b については型とメモリ上のアドレス相対位置が一致するので struct A と struct B の変数は a と b については気にせずアクセスできる。
c については、 struct A の変数を struct B としてキャストすればアクセスはできるだろうが、読み込めるのはそのアドレスにあるデータなのでどうなるかは不明だ。まあ alignment はpack(0) とかしてなければ問題ないので死にはしないだろう。

結局のところ、メモリ上にあるものはすべて「データ」に過ぎない。
それがプログラムなのか文字列なのかExcelのデータなのかというのは後付けの理由だ。画像データなんてフォーマット以外の部分はバイナリデータに過ぎないので、そこにプログラムとして動作する値があったとしても不思議ではない。ウイルスだったりセキュリティホールだったりはそういうところからやってくるんだろう。

 

さて話を戻して。

struct の説明で、メンバの並びが違うと違うものとして扱われるというのをどこかで読んだ気がする。まあ、私は C言語のことを思い浮かべながら Go言語を見ているので、書いてなくても「きっとそうなんだろう」と思い込んでいるだけかもしれない。

埋め込みだとどうなのか、ちょっと確認してみよう。

package main

import "fmt"

func main() {
    // annonymous field
    type Name struct {
        name string
    }
    type Age struct {
        age int
    }
    type User1 struct {
        Name
        Age
    }
    type User2 struct {
        Age
        Name
    }

    var u1 User1
    var u2 User2
    u1.name = "kuma"
    u1.age = 98
    u2.age = u1.age
    u2.name = u1.name
    fmt.Printf("u1=%v\n", u1)
    fmt.Printf("u2=%v\n", u2)
    fmt.Printf("compare: %v\n", u1 == u2)
}

最後の u1 == u2 がコンパイルエラーになる。

invalid operation: u1 == u2 (mismatched types User1 and User2)

ただ・・・これはAge と Name が違うからではなく、純粋に struct で定義したものが違うからエラーにしているだけだと思う。並びを同じにしてもエラーになるからだ。

 

多少異なるのがキャストについてだ。
埋め込む順番を同じにした場合、 User2(u1) も User1(u2) もエラーにならない。

しかし Name(u1) も Name(u2) もエラーになる。
そこは通してやってもいいんじゃないかと思ったが、そうすると Age(u1) なんかも通さないといけなくなってしまうのかもしれない。メンバの先頭だけ OK というのは説明しづらいからな。