kakts-log

programming について調べたことを整理していきます

google/wireでDIする際のgenerics対応について

概要

この記事はQiita Advent Calendar 2023 Go言語の第十七日の記事です。

qiita.com

google/wireについて

github.com

google/wireとは、GoでのDependency Injection用のコード生成ツールとなります。
wire自体の使い方は、公式のチュートリアルとしてまとめられているため、そちらをご確認ください。 github.com

google/wireの現状について簡単にまとめると以下の通りになります。

Wire is currently in maintenance mode (i.e. not accepting new features) and investigation is needed for how much work is involved. Full support of go generics will probably not land anytime soon, but we welcome comments and fixes.

Goにおけるgenerics対応済みのDIツールは他に存在しますが、今回はgoogle/wireにおけるgenericsを扱う場合のワークアラウンドの方法についてまとめます。
 

genericsを用いたコードのDIでエラーが出る例

雑コードですが、下記のV interfaceを定義し、 Message 構造体にint型のフィールドを定義します

type V interface {
    int | float32
}

func NewV[T V]() T {
    return T(2)
}
package modelgen

import "fmt"

type Message struct {
    Msg string
    V   int
}

func NewMessage(v int) Message {
    return Message{Msg: "Hi there!", V: v + 1}
}

type Greeter struct {
    Message Message // <- adding a Message field
}

func NewGreeter(m Message) Greeter {
    return Greeter{Message: m}
}

func (g Greeter) Greet() Message {
    return g.Message
}

type V interface {
    int | float32
}

func NewV[T V]() T {
    return T(2)
}

type Event struct {
    Greeter Greeter // <- adding a Greeter field
}

func NewEvent(g Greeter) Event {
    return Event{Greeter: g}
}

func (e Event) Start() {
    m := e.Greeter.Greet()
    fmt.Println(m.Msg, m.V)
}

これをDIさせるために、wire.goで下記のように設定します。 Vインタフェースの実装に関するプロバイダ関数であるNewVをwire.Buildに型パラメータの指定なしで渡してみます。

func InitializeGenericsEvent() modelgen.Event {
    wire.Build(modelgen.NewEvent, modelgen.NewGreeter, modelgen.NewMessage, NewV)
    return modelgen.Event{}
}

この状態でwireコマンドを実行すると、下記のエラーとなり、型パラメータTが指定されていないとエラーが出ます。

$ make wire
wire
wire: /Users/hoge/Documents/code/go-wire-sandbox/wire.go:128:2: cannot infer T (/Users/hoge/Documents/code/go-wire-sandbox/src/modelgen/generics_models.go:30:11)
wire: generate failed
make: *** [wire] Error 1

次に、NewV[int]と型パラメータTにintを指定すると、unknown patternエラーがでて、 wireでは認識できないシンタックスエラーとなります。

// Generics動作確認用
func InitializeGenericsEvent() modelgen.Event {
    wire.Build(modelgen.NewEvent, modelgen.NewGreeter, modelgen.NewMessage, modelgen.NewV[int])
    return modelgen.Event{}
}
➜  go-wire-sandbox git:(main) ✗ make wire
wire
wire: /Users/hoge/Documents/code/go-wire-sandbox/wire.go:128:74: unknown pattern
wire: github.com/kakts/go-wire-sandbox: generate failed
wire: at least one generate failure
make: *** [wire] Error 1

解決策: 型パラメータを指定してDIさせる方法

上記の問題がありますが、下記の通り、型パラメータを指定して関数を呼び出した結果を返すプロバイダー関数(NewVInt)を定義し、それをwire.Buildに渡してあげると解決します。

func NewVInt() int {
        // intを型パラメータに指定してNewVを実行し、その戻り値を返す
    return modelgen.NewV[int]()
}

// Generics動作確認用
func InitializeGenericsEvent() modelgen.Event {
    wire.Build(modelgen.NewEvent, modelgen.NewGreeter, modelgen.NewMessage, NewVInt)
    return modelgen.Event{}
}
$ make wire
wire
wire: github.com/kakts/go-wire-sandbox: wrote /Users/hoge/Documents/code/go-wire-sandbox/wire_gen.go

wireによって生成されたコードは下記のようになり、 型パラメータで指定したint型の値をNewMessageの引数に渡していることを確認できます。

func NewVInt() int {
    return modelgen.NewV[int]()
}


// Generics動作確認用
func InitializeGenericsEvent() modelgen.Event {
    int2 := NewVInt()
    message := modelgen.NewMessage(int2)
    modelgenGreeter := modelgen.NewGreeter(message)
    event := modelgen.NewEvent(modelgenGreeter)
    return event
}

これでwire.Buildでgenericsを使ったプロバイダー関数を定義し、型パラメータを指定した状態でDIさせることができました。

最後に

google/wireにおけるgenericsの対応についての説明は以上となります。

序盤の概要にまとめましたが、google/wireは現在メンテナンスモードになっており、今後のgoの新機能に追従することはないようです。しかし上述したようにgenericsをどうしても使いたい場合の解決策があることがわかりました。

今後新規で開発をする場合は、運用上困らないように他のDIツールの検討をしてみても良いかもしれません。

備考リンク

Update dependencies to support Generics by efueyo · Pull Request #360 · google/wire · GitHub