排他なフィールドとinvalidなzero valueでハマった話
goで排他なフィールドとinvalidなzero valueではまった。 (短く言うならポインターにするべきところでポインターになっていなかったという話)
排他なフィールド
排他なフィールドを持っているstructと言うのはこういう形。
// T : type T int // const ( TX = T(0) TY = T(1) ) // A : type A struct { T T X X Y Y } // X : type X struct { } // Y : type Y struct { }
Aというstructは、Xに値が入る場合もあるし。Yに値が入る場合もある。 どちらに値が入るかはTの値で決まる。
以下の値は、Xに値が入るもの
main.A{
T: 0,
X: main.X{},
Y: main.Y{},
}
invalidなzero value
invalidなzero valueというのは初期状態で何かの操作を行うとエラーになるようなもののこと。
例えば、bson.Marshalするときに、bson.ObjectIdという型の値を使うことがあるのだけれど。
このbson.ObjectIdのzero valueはinvalid。適切なものにするには bson.NewObjectId()
にしないとだめ。
例えばこういう型があったとして
// O : type O struct { ID bson.ObjectId `bson:"_id"` }
正常な値
正常に初期化した値はbson.Marshalが動く。
{ o := O{ID: bson.NewObjectId()} fmt.Println("initialized", o) b, err := bson.Marshal(o) if err != nil { log.Fatal("ng", err) } fmt.Println("ok", b) }
大丈夫。
initialized {ObjectIdHex("594c9af90ccee09cd63ea023")} ok [22 0 0 0 7 95 105 100 0 89 76 154 249 12 206 224 156 214 62 160 35 0]
不正な値
zero valueは不正なのでbson.Marshalはだめ。
{ // error is occured var o O fmt.Println("zero", o) b, err := bson.Marshal(o) if err != nil { log.Fatal(err) } fmt.Println("ok", b) }
だめ。
zero {ObjectIdHex("")} 2017/06/23 13:37:13 ObjectIDs must be exactly 12 bytes long (got 0)
排他なフィールドとinvalidなzero value
排他なフィールドとinvalidなzero valueと言うのは先ほどの2つが組み合わさった場合の話。
先程のAの型(XとYが排他なフィールド)が以下の様に変わった場合に
// A : type A struct { T T X X Y Y } // X : type X struct { ID bson.ObjectId `bson:"_id"` } // Y : type Y struct { V int `bson:"v"` }
Yの値だけが入った値(正常なもののつもり)をbson.Marshalしようとするとエラーになる。 理由は単純でA.Xのzero valueが不正な値なため。
a := A{T: TY, Y: Y{V: 10}} // 正常な値のつもり // aの値 main.A{ T: 0, X: main.X{ ID: ObjectIdHex(""), // 不正なzero value }, Y: main.Y{ V: 10, }, } // ObjectIDs must be exactly 12 bytes long (got 0)
Xはポインターになっている必要がある。