Goのインターフェースとポインタについて完全に理解してみた
概要
Go自体は少し業務で触ったりはするのですが、深く理解する機会もあまりないので、
勉強も兼ねて興味本位で最近webアプリを作ってるGolangぴよぴよエンジニアです。
そんな中でdigを使って依存の解決をしようと思ったら、ポインタの概念とインターフェースの概念が色々混じり合ったことで完全にGo迷子になったのでその辺りを整理してみました。
※digの話は本編に関係ないので深掘りはしません
前提
一応前提としてInterfaceのあるプログラミング言語だと、
Interfaceは抽象的なものであり、規格なので、その抽象に依存している具体側が規格に沿って実装しないといけないというくらいの理解はしています。
とりあえず実装してみる
まずはdigのドキュメント通りにとりあえずコンストラクタを実装してみた。
これは戻り値に*
と記述してHandler
のポインタを期待しているし、returnしているのも&
でHandler
のポインタを返している。
type Handler struct {
...
}
func NewFugahandler() *Handler {
return &Handler{}
}
ここまではGoのポインタの意味が分かればふむふむ。なるほどと言った感じ。
依存性を逆転するためにインターフェースを用いた場合
なるほど。まあドキュメント通りにやればいけるのね。
と思って、さっきのHandler
のように次のようなコードを書いてみた。
type Port interface {
...
}
type Gateway struct {
...
}
func NewFugaPort() *Port {
return &Gateway{}
}
ぱっと見さっきと同じやからいけそうですよね?
それがなぜかうまくいかないわけなんですよ〜
(ちなみにChatGPTにも一応聞いてみたけどまあ同じことを教えてきた)
cannot use &Gateway{} (value of type *Gateway) as *Port value in return statement: *Gateway does not implement *Port (type *Port is pointer to interface, not interface)
return 文の *Port 値として &Gateway{}(*Gateway 型の値)は使用できません: *ゲートウェイは *Port を実装していません(*Port 型はインターフェイスへのポインタであり、インターフェイスではありません)。
インターフェースのポインタ???
まあなんとなく言ってることは分かる。
インターフェースにもポインタはあるであろう。
でも実装側のGatewayがポインタを返してるし、
期待値が抽象であるインターフェースのポインタなら、その具体のポインタを返したらいけませんの??
# これはいける
*Handler ← &Handler
# これはダメ
*Port ← &Gateway
インターフェースを用いてるのが原因ではあるのだろうけど、
仕組みが理解できませんわ....
完全に理解してみる
ドキュメントから色々情報を漁ってみた。
Interface types¶
An interface type defines a type set. A variable of interface type can store a value of any type that is in the type set of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.
なるほど。インターフェースとは型の集合であると。(書いてることそのままやけど)
まあつまりインターフェースにはポインタ型も格納できるみたいですね。
試してみた
これでgo run main.go
を実行してみる
package main
import "fmt"
type Port interface {
Hoge()
}
type Gateway struct {
}
func (g *Gateway) Hoge() {
println("Hoge")
}
func main() {
var a Gateway = Gateway{}
var b *Gateway = &Gateway{}
var c Port = &Gateway{}
// error: Gateway does not implement Port (missing Hoge method)
// var d Port = Gateway{}
fmt.Printf("[%T] %+v\n", a, a)
fmt.Printf("[%T] %+v\n", b, b)
fmt.Printf("[%T] %+v\n", c, c)
}
結果
c
には&Gatewayのポインタ型が格納されてますね。
> go run main.go
[main.Gateway] {}
[*main.Gateway] &{}
[*main.Gateway] &{}
つまりさっきのエラーtype *Port is pointer to interface, not interface
は
インターフェース自体が型集合のため、構造体のポインタ型も含んでいるので
インターフェースのポインタ => 構造体のポインタのポインタ
を求められてるみたいにしてしまってたということですね。
(厳密にはインターフェースのポインタが実装されてないって言ってるからちょっと違うけど)
番外編
ちなみにさっきの状態だと、メソッド名が衝突してインターフェースに構造体を格納するパターンを試せなかったので別途やってみた。
var d Port = Gateway{}
fmt.Printf("[%T] %+v\n", d, d)
> [main.Gateway] {}
まあ普通に構造体が格納されてますね。
まとめ
完全に理解した今となってはエラーに書いてたことその通りやな〜て感じでした!
わかった後に見ると、何を当たり前のこと言ってるんやって感じるこういう現象に名前をつけたい。
参考
Interface_types
"<type> is pointer to interface, not interface" confusion
Discussion