Posted at

Goでオブジェクト生成の返り値にinterfaceを使うとnil Pointer Receiverができないという話

タイトルそのままです。

package main

import (
"fmt"
"reflect"
)

type Fooer interface {
Foo()
}

type Fooo struct{}

func (f *Fooo) Foo() {
if f == nil {
fmt.Println("nil!!")
} else {
fmt.Println("not nil!!")
}
}

func newFooer() Fooer {
return &Fooo{}
}

func bar(f Fooer) {
f.Foo()
}

func main() {
// 型を代入したあとnilを入れても型情報は消えない
z := &Fooo{}
fmt.Println(reflect.TypeOf(z)) // *main.Fooo
z = nil
fmt.Println(reflect.TypeOf(z)) // *main.Fooo
z.Foo() // nil!!
bar(z) // nil!!

// 型情報を持ったnilを入れて、そのあとただのnilを入れても型情報は消えない
y := (*Fooo)(nil)
fmt.Println(reflect.TypeOf(y)) // *main.Fooo
y = nil
fmt.Println(reflect.TypeOf(y)) // *main.Fooo
y.Foo() // nil!!
bar(y) // nil!!

// interfaceを返す関数から変数を代入したあとにnilを入れると型情報が消える
x := newFooer()
fmt.Println(reflect.TypeOf(x)) // *main.Fooo
x = nil
fmt.Println(reflect.TypeOf(x)) // <nil>
// panicになる x.Foo()
// panicになる bar(x)
}

発端はクリーンアーキテクチャをやろうと思いサンプルを探すと、この例のように生成系でinterfaceを返しているものが多いです。

依存関係の逆転をしたいので、接合部分をinterfaceにするというのは素直だと思いますが、Goでは微妙なところもありそうです。

Go言語のInterfaceの考え方、Accept interfaces,return structsに従ったほうが、大体は良さそう。

クリーンアーキテクチャをするならAccept interfaces,return structs に従って生成してからDIで渡すところでinterfaceを使うようにしたほうが良さそうです。