タラバガニー設計局stalins.clubNOTE/notes/must-do-declined

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] が消えたことにある。残る TTt ごとに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.Must alone unfortunately seems to be an attractive nuisance. A general must.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 *Must APIs 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.mdmust-problem / must-workaround / must-127 / must-proposal / must-declined スライド

▸ ノート一覧に戻る