2019/07/02

Recent entries from same category

  1. Go 言語プログラミングエッセンスという本を書きました。
  2. errors.Join が入った。
  3. unsafe.StringData、unsafe.String、unsafe.SliceData が入った。
  4. Re: Go言語で画像ファイルか確認してみる
  5. net/url に JoinPath が入った。

Go は Ducktype をサポートしたプログラミング言語です。interface で宣言されたメソッドを型に強制したり問い合わせたり出来ます。メソッドの実装を保証する為に以下の様に struct に interface を embedded する事も出来ます。

package main

type I interface {
    doSomething()
}

type foo struct {
    I
}

func (f *foo) doSomething() {
}

func callSomething(i I) {
    i.doSomething()
}

func main() {
    callSomething(&foo{})
}

ただしこうしてしまうと型 foo の情報にインタフェースの情報 I が含まれる事になり、struct のサイズが数バイト(アーキテクチャによります)大きくなってしまうのです。

package main

import (
    "fmt"
    "unsafe"
)

type I interface {
    doSomething()
}

type foo struct {
    I
}

func (f *foo) doSomething() {
}

type bar struct {
}

func (b *bar) doSomething() {
}

func main() {
    fmt.Printf("sizeof(%T) is: %v\n", foo{}, unsafe.Sizeof(foo{}))
    fmt.Printf("sizeof(%T) is: %v\n", bar{}, unsafe.Sizeof(bar{}))
}

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

64bit の OS では16バイトです。配列になればもっと大きなサイズになってしまいますね。embedded をやめてしまってもコンパイル時に気付けるので問題ないのですが、ライブラリパッケージの場合はそれを使用するコードが無いのでテストを実施するまで気付けない事になります。なんだか時間が勿体ないですよね。そこで以下の様におまじないを入れておきます。

package zoo

var _ I = (*foo)(nil)

type I interface {
    doSomething()
}

type foo struct {
}

func (f *foo) doSomething() {
}

こうする事で、万が一 interface のメソッド宣言と struct のメソッドが不一致であっても即座に気付ける事になります。最近の Language Server を実装したテキストエディタであれば書いた瞬間にエラーになってくれるので、CI で実行するまで気付けないといった時間の無駄を削減する事が出来ます。

この方法は Go の FAQ にも書かれており、Go 本体のソースコードにも沢山書かれているテクニックです。

How can I guarantee my type satisfies an interface?

ぜひ無駄のない Go ライフを。

Posted at by