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

Go Proposal Weekly Digest

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

#47487declined

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

ステータス変更: likely_decline declined

要約

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

概要

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

ステータス変更

likely_declinedeclined
2026年6月25日の週次レビューで「likely decline」と判定された後、7月8日の会合でも新たな合意形成の動きがなく「no change in consensus」として正式に declined(却下)となりました。関連する、より汎用的な「インターフェースリテラル」提案(#25860)の方が有望であるとの判断(@adonovanによるエコシステム調査で多メソッドインターフェースへの活用例が多数見つかったこと)が大きく影響しています。

技術的背景

現状の問題点

io.Readerio.Writer のような単一メソッドインターフェースを、クロージャで手軽に実装したい場面は多くあります。しかし現在のGoでは、関数型に直接メソッドを持たせることはできないため、以下のようなラッパー型を毎回定義する必要があります。

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
}

これは本質的なロジック(w.n += int64(n))に対して構文的に重く、カプセル化を破っているようにも見えてしまいます。

提案された解決策

シグネチャが一致する関数値を、単一メソッドインターフェースへ明示的な型変換(代入ではなく T(x) という conversion)として許可する案でした。

cw := io.Writer(func(p []byte) (n int, err error) {
    n, err = os.Stdout.Write(p)
    N += int64(n)
    return n, err
})

実装方式としては、コンパイラが runtime パッケージ内に非公開の「仮想的な」defined type を自動生成し、そこにメソッドを生やして関数を呼び出す、という案が有力視されていました。これにより、既存の「メソッドを持てるのはdefined typeのみ」という不変条件を壊さずに済みます。一方で #21670(暗黙の代入による自動実装)とは異なり、変換を明示的に要求することで、io.Readerio.Writer のように同一シグネチャを持つ複数のインターフェースへの意図しない誤実装を防ぐ狙いがありました。

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

主なユースケースは io.Reader / io.Writer / io.Closer など、標準ライブラリに多数存在する単一メソッドインターフェースを、使い捨てのクロージャで簡潔に実装することです。http.HandlerFunc パターンを毎回手書きすることなく、その場限りの実装を1行〜数行で書けるようになるはずでした。

コード例

// Before: ラッパー型を定義する必要がある
type countingWriter struct {
    w io.Writer
    n int64
}
func (w *countingWriter) Write(p []byte) (int, error) {
    n, err := w.w.Write(p)
    w.n += int64(n)
    return n, err
}
cw := &countingWriter{w: os.Stdout}
// After: 提案されていた記法(実現せず)
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
})

議論のハイライト

  • dynamic typeの扱いを巡る長期論争: 変換後のインターフェースの動的型を「コンパイラ生成の不透明な型」にするか(提案者Meroviusの案)、「func(...) そのもの」にするか(@rscの案)で意見が割れました。前者はreflectや型アサーションへの影響が小さい一方「動的型が何であるか分からなくなる」という欠点があり、後者は逆にreflectやインターフェース実装ルールへの侵襲的な変更が必要でした。最終的にこの対立は解消されないまま2021年に一度議論が停滞しています。
  • 曖昧性の問題: @ianlancetaylorが指摘したように、変換対象の関数値がすでに何らかのメソッドを持つ型(同シグネチャの別インターフェース由来など)だった場合、どちらの実装を使うべきか曖昧になるという技術的懸念がありました。
  • 非公開メソッドの迂回実装問題: @findleyrが指摘した通り、この変換により非公開(unexported)メソッドを持つインターフェースを外部パッケージから実装できてしまう可能性があり、仕様の慎重な記述が必要とされました。
  • 代替案との比較: 2025年に@griesemerが実装可能な既存の回避策(http.HandlerFunc型パターンなど、わずか数行で実現可能)を整理した結果、この提案がもたらす利便性がコストに見合うか疑問視されました。最終的に@adonovanが、より汎用的な「インターフェースリテラル」提案(#25860)の方が実際のエコシステムで多メソッドインターフェースにも活用できる例が多く、優先すべきだと判断しました。
  • 標準ライブラリでの部分的解決の提案: @ianlancetaylorはio.ReaderFunc/io.WriterFuncのような専用アダプタ型を標準ライブラリに追加する代替案を提示しましたが、@deefdragonらから「1メソッドインターフェースごとにヘルパー関数を追加するパターンが乱立する」懸念が示され、この案も保留となりました。

関連リンク