spec: allow explicit conversion from function to 1-method interface
要約
概要
関数値(function value)を1つのメソッドを持つインターフェース型へ明示的に型変換できるようにするGo言語仕様の変更提案です。http.HandlerFunc のようなボイラープレート型を毎回定義する手間を省き、クロージャを直接インターフェースとして扱えるようにします。
ステータス変更
(hold) → active
2026年5月27日のProposal Review Meeting(@adonovan, @griesemer, @ianlancetaylor 他)において、本提案がactiveとして議事録に追加されました。以前はプロトタイプ実装(go.dev/cl/572835)の完成を待ちholdとされていましたが、同CLにより実装が提供されたことを受け、@ianlancetaylorがholdを解除し、週次Proposal Review Meetingでのレビュー対象に復帰しました。
技術的背景
現状の問題点
Goで関数値をインターフェースとして使う場合、毎回アダプター型を定義する必要があります。たとえば io.Writer を実装したカウンティングラッパーを書くには、専用の構造体と Write メソッドを定義しなければならず、ロジックの本質(バイト数の加算)がボイラープレートに埋もれます。
// 現状: 構造体型とメソッド定義が必要
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
}
標準ライブラリ内でも、io.Reader / io.Writer / io.Closer を実装するためだけの関数型が8つテスト内に存在することが確認されています。
提案された解決策
Go仕様の変換ルール一覧に新たな項目を追加します。
型
Tがちょうど1つのメソッドを持つインターフェース型であり、変換対象の値xの型がTの唯一のメソッドのシグネチャと一致する関数型である場合、xをTへ明示的に変換できる。
変換により、コンパイラが内部的に自動生成した名前付き型(例:io.Reader.func)を経由してインターフェースが実現されます。これによりreflectでのデバッグや型アサーションの整合性が保たれます。
これによって何ができるようになるか
クロージャをアダプター型なしに直接インターフェース値として使えるようになります。ローカル状態を持つ軽量なインターフェース実装をその場で記述でき、テストコードや一度しか使わないアダプターのボイラープレートを大幅に削減できます。
コード例
// Before: カウンティングライターの現在の書き方
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")
}
// 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")
}
// HTTPラウンドトリッパーのラッパーも同様に簡潔に書ける
func addUserAgent(next http.RoundTripper, ua string) http.RoundTripper {
return http.RoundTripper(func(req *http.Request) (*http.Response, error) {
req1 := req.Clone(req.Context())
req1.Header.Set("User-Agent", ua)
return next.RoundTrip(req1)
})
}
議論のハイライト
- #21670との差別化: 類似提案 #21670 は「暗黙の代入可能性(assignability)」を提案するのに対し、本提案は「明示的な変換(explicit conversion)」のみを求める。これにより
io.Readerとio.Writerが同一の関数シグネチャを持つ曖昧さの問題を回避できる。 - 内部型の名前設計: 型アサーションやreflect対応のため自動生成型に名前が必要との議論があり、
io.Reader.funcのような「参照はできないが人間が読める名前」とする案をrscが提示し採用方向となった。 - 未エクスポートメソッドへの変換制限: ライブラリが未エクスポートメソッドを使ってインターフェース実装者を制限するパターン(Option patternなど)を壊さないよう、変換は変換箇所からアクセス可能なメソッド名に限定すべきとの懸念が最近提起されている。
- プロトタイプ実装: コアチーム(rsc, ianlancetaylor, griesemer, robpike)が「試す価値がある変更」と判断し、go.dev/cl/572835 にてプロトタイプが実装・公開されており、実際の動作確認が可能な状態になったことがholdからactiveへの移行を後押しした。
- インターフェースリテラル(#25860)との競合: 複数メソッドを持つインターフェースにも対応できる代替提案 #25860(
io.Writer{Write: myFunc}構文)との比較が続いており、単一メソッドへの限定とより汎用的な構文のどちらが採用されるか最終決定はまだなされていない。