must: Do が却下された理由
テストで「エラーなら t.Fatal したい」ためのヘルパを作ろうとすると、多値の呼び出しを他の引数と混ぜられないという Go の制約にぶつかる。
func Must[T any](t *testing.T, v T, err error) T // panic ではなく t.Fatal したい
u = Must(t, url.Parse("https://go.dev"))
// NG! multiple-value url.Parse(...) in single-value context
Go 1.26 まではこれを2段構えで回避するのが定番のイディオムだった。値をいったん型パラメータを運ぶためだけの中間構造体に包み、t はメソッドで後から受け取る。
type MustExpect[T any] struct{ v T; err error } // T を運ぶためだけの型
func Must[T any](v T, err error) MustExpect[T]
func (m MustExpect[T]) Return(t *testing.T) T
u := Must(url.Parse("https://go.dev")).Return(t)
Go 1.27 で ジェネリクスメソッド (Go 1.27) が入ると、これを1段で書ける。
type TT struct{ *testing.T }
func (t TT) Must[V any](v V, err error) V
tt := TT{t}
u := tt.Must(url.Parse("https://go.dev"))
本質的な変化は、呼び出す値ごとに生まれていた MustExpect[T] が消えたことにある。残る TT は t ごとに1回だけ作る包み紙にすぎない。TT という型そのものが必要なのは、Go には他パッケージの型 (*testing.T) にメソッドを生やす手段が無いためである。
提案の経緯と却下
must.Do 相当の提案 #54297 は 2022-08 に提出されたが、しばらく塩漬けになっていた。理由のひとつが「メソッドにできないから」だった。2026 年に本体提案 #77273 "A change of view." が accepted になったのを受けて再始動し、testing.T.Must として t を持ち回らずに書ける案が具体化した。
しかし 2026-06-25 に declined となった。aclements の最終コメントが決め手になっている。
testing.T.Mustalone unfortunately seems to be an attractive nuisance. A generalmust.Do(whatever you call it) seems an even riskier attractive nuisance: an incremental nicety that could have very negative effects at ecosystem scale. We've seen misuse even of the existing one-off*MustAPIs that has led to outages.
既存の一発芸的な *Must API の誤用がすでに実際の障害につながっている、という実績が却下の根拠になっている。attractive nuisance という語彙がここで使われている。
言語機能が入っても、それが標準ライブラリの API として入るとは限らない。ただし自分の手元で書くぶんには、Go 1.27 からこの1段構えの書き方が使える。標準に入った唯一のジェネリクスメソッドについては 標準ライブラリ唯一のジェネリクスメソッド rand.N を参照。
出典
- #54297
- Go Release Party 1.27 の発表資料リポジトリ
issues.md§4、slide/deck.mdのmust-problem/must-workaround/must-127/must-proposal/must-declinedスライド