Golangの構造体比較

Goでは構造体を == で比較できるらしい。

package main

import (
    "fmt"
    "unsafe"
)

type Hoge struct {
  t bool
  d int
  s string
}

func main() {
    a := Hoge{t: true, d: 1, s:"nyan"}
    b := Hoge{t: true, d: 1, s:"nyan"}
    c := a   // 値コピー
    d := &a  // ポインタコピー
 
    fmt.Println(a == b)    // true 値の比較、同値
    fmt.Println(&a == &b)  // false ポインタの比較、同一ではない
    fmt.Println(a == c)    // true 値がコピーされたので同値
    fmt.Println(&a == &c)  // false 値がコピーされたので同一ではない
    fmt.Println(a == *d)   // true ポインタのコピーなので同値
    fmt.Println(&a == d)   // true ポインタのコピーなので同一 
}

どうせ同一性の比較しかできんのだろうと思っていたら、別のインスタンスでも、フィールドの値を比較して同値と判断してくれるようだ。

   fmt.Println(unsafe.Sizeof(a))     // 16
    fmt.Println(unsafe.Sizeof(a.t))   // 1
    fmt.Println(unsafe.Sizeof(a.d))   // 4
    fmt.Println(unsafe.Sizeof(a.s))   // 8
 
    fmt.Println(unsafe.Offsetof(a.t))  // 0
    fmt.Println(unsafe.Offsetof(a.d))  // 4
    fmt.Println(unsafe.Offsetof(a.s))  // 8

Go playgroundではbool型の後ろに3byteパディングがある(どうやら32bit環境らしいw)

どう比較しているのだろう? フィールドを一つ一つ比較するのはコストが高そうなので、構造体の開始部分から16byteのメモリの中身を丸ごと比較してるのだろうか? だとするとパディング部分も初期化時に0埋めされてないと比較できない。まあ、セキュリティ的な観点からみるとパディングも0埋め初期化するのが正解だが…、ドキュメントを見てもよくわからん。

構造体の比較は便利そうではあるが、ポインタをフィールドに含む場合を想定すると面倒だし、スライスを持っているとそもそも比較できない。そういった面倒ごとを考慮せず同値比較するならば、reflect.DeepEqualを使うといいらしい

   fmt.Println(reflect.DeepEqual(&a, &b))   // true

絶対ハマるらしいgolangのnilにハマった件

ハマりましたwwww

func main() {
    var e1 error
    e1 = x()
    if e1 == nil {
        fmt.Printf("e1 == nil: %v\n", e1)
    } else {
        fmt.Printf("e1 != nil: %v\n", e1)
    }

    var e2 *NyanError
    e2 = x()
    if e2 == nil {
        fmt.Printf("e2 == nil: %v\n", e2)
    } else {
        fmt.Printf("e2 != nil: %v\n", e2)
    }
}

func x() *NyanError {
    return nil
}

// エラー
type NyanError struct {
}

func (e *NyanError) Error() string {
    return "にゃー"
}

出力

e1 != nil: にゃー
e2 == nil: にゃー

どゆこと?

https://play.golang.org/p/z5pdAdI0YZ

正直go書き始めて3日程度の俺にはよくわからんのだが、errorのようなinterface型の変数は、実行時にnilが渡されたとしても渡されるであろう構造体の型情報を保持しているらしい。んで、この型情報も一致しないと == nil にならないらしいマジすか。nilリテラルは値がnilで型もnilだが、e1は値がnilで型がNyanError、なので不一致。

値はnilなのに、 fmt.Printf(“%v”, e1) で “にゃー” とか表示されるのは変数が型情報を持っているからなんすね…

interface型変数の場合 e == nil || reflect.ValueOf(e).IsNil() と判定するといいらしい。これは、変数の型情報を無くして値だけで比較する方法って事ですか。

しかし、変数の型が構造体型かinterface型かで動き変わるなんて思わんかったよw