メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#47487likely_decline

spec: allow explicit conversion from function to 1-method interface

ステータス変更: active likely_decline

要約

AIによる要約であり、誤りを含む場合があります。

概要

関数値を1メソッドインターフェースへ明示的に型変換できるようにするという言語仕様変更の提案。io.Writer(someFunc) のような構文で、シグネチャが一致する関数をインターフェースに変換できるようにすることを目的としていた。

ステータス変更

activelikely_decline
2026年6月24日の提案レビューグループ会議(@adonovan, @bradfitz, @cherrymui, @griesemer, @ianlancetaylor, @neild, @rolandshoemaker)において、本提案は「likely decline(却下見込み)」と判断された。主な理由は以下の通り。まず、@adonovan が代替案であるインターフェースリテラル提案(#25860)のエコシステム調査を行った結果、複数メソッドインターフェースへの適用機会が豊富であり、その提案のほうが優れていると判断した。また、griesemer によるまとめでは、設計上の未解決問題(関数変数の曖昧性問題、アンエクスポートメソッドの実装問題)が残っており、既存のワークアラウンドが過度に負担でもないことが指摘された。

技術的背景

現状の問題点

io.Readerio.Writer などの1メソッドインターフェースを関数リテラルで実装したい場合、現在は必ずアダプター型を別途定義する必要がある。

// 現状: アダプター型の定義が必要
type countingWriter struct {
    w io.Writer
    n int64
}
func (w *countingWriter) Write(p []byte) (n int, err error) {
    n, err = w.w.Write(p)
    w.n += int64(n)
    return n, err
}
func main() {
    cw := &countingWriter{w: os.Stdout}
    // cw を使って書き込み
    fmt.Println(cw.n, "bytes written")
}

この問題は標準ライブラリ内でも顕在化しており、net/http パッケージでは funcWriterwriterFunc という名前で同一の目的のアダプター型が二重に定義されていることが Merovius によって指摘された。

提案された解決策

Goの言語仕様の変換規則に以下のルールを追加する提案。

T が正確に1つのメソッドを持つインターフェース型であり、x の型がそのメソッドのシグネチャと同一の関数型である場合、T(x) という型変換を許可する」

// After: 提案された構文
func main() {
    var N int64
    cw := io.Writer(func(p []byte) (n int, err error) {
        n, err = os.Stdout.Write(p)
        N += int64(n)
        return n, err
    })
    // cw を使って書き込み
    fmt.Println(N, "bytes written")
}

実装としては、コンパイラが非公開の仮想型(例: runtime.io_writer)を自動生成し、その型に対してメソッドを持たせる形式が提案された。

これによって何ができるようになるか

  1. クロージャーを使って io.Reader / io.Writer に直接バインドでき、ローカル状態の管理が容易になる
  2. テストコードでのモックインターフェース実装が簡潔になる(現状では標準ライブラリ内テストでも同一目的のアダプター型が複数作られている)
  3. http.HandlerFunc のようなアダプターパターンを各パッケージで独自定義する必要がなくなる

コード例

// Before: http.HandlerFunc パターンに相当するものを自分で定義
type myHandlerFunc func(w http.ResponseWriter, r *http.Request)
func (f myHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    f(w, r)
}
var h http.Handler = myHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello")
})
// After: 提案の構文(採用されなかった)
var h http.Handler = http.Handler(func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello")
})

議論のハイライト

  • 暗黙的代入 vs. 明示的変換: 元の類似提案 #21670 は関数が直接インターフェースを実装する(暗黙的代入可能)という内容だったが、本提案は明示的型変換のみに限定することで io.Readerio.Writer の混同問題を回避しようとした点が主な差別化ポイントだった。
  • 曖昧性問題: @ianlancetaylor が指摘した通り、すでに一致するメソッドを持つ関数型変数を変換する場合に、コンパイラが「既存のメソッドを使う」か「新しいラッパーを生成する」かを判断できない曖昧なケースが生じる可能性があった。
  • アンエクスポートメソッドの意図的制限を破る問題: @findleyr が指摘したように、ライブラリがオプションパターン等で意図的にメソッド名を非公開にしてAPIを制限している場合、この提案では外部から実装を注入できてしまい、ライブラリ作者の意図を破る可能性があった。
  • reflect パッケージの変更: 自動生成される仮想型がどのような名前で reflect.TypeOf() に表示されるか、型アサーションをどう扱うかについて合意が得られなかった。
  • インターフェースリテラル (#25860) との競合: @adonovan のエコシステム調査で、複数メソッドインターフェースに対応できる #25860 のほうがユースケースが多いことが示され、単一メソッドに限定した本提案より優れていると判断されたことが最終的な却下判断の決め手となった。

関連リンク