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

Go Proposal Weekly Digest

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

#21111active

testing: allow examples to return an error

新規提案

要約

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

概要

testingパッケージのExample関数(ExampleXxx形式)がerrorを戻り値として返せるようにするproposalです。現在はExample関数が戻り値を持てないため、エラー処理のコードが煩雑になり、実際のアプリケーションコードを代表するサンプルとして機能しにくい問題を解決します。

ステータス変更

**** → active
2026年5月27日のProposal Review Meetingにて、aclements氏がこのproposalをactiveカラムに追加し、週次レビューの対象としました。2018年に「Go 2のエラーハンドリング構文改善を待つ」として保留(hold)とされていましたが、エラーハンドリング構文改善が無期限に延期されると決定されたため(2025年12月にadonovan氏がholdラベルを除去)、改めてactiveとして議論が再開されることになりました。

技術的背景

現状の問題点

Example関数は現在、戻り値を持てません。そのため、エラーを返す関数を呼び出すコードを含む例では、以下のような不自然な実装を強いられます。

// 現在の書き方: panicやlog.Fatalに頼るか、内部関数でラップする必要がある
func ExampleWriteTempFile() {
    example := func() (err error) {
        filename, cleanup, err := WriteTempFile("", "example-*", []byte("example"))
        if err != nil {
            return err
        }
        defer cleanup(&err)
        fmt.Printf("Temporary example file name: %s\n", filename)
        return nil
    }
    err := example()
    if err != nil {
        panic(err)
    }
    // Output: ...
}

標準ライブラリの例(os.CreateTempなど)でも、deferlog.Fatalを組み合わせており、deferが実行されないコードが含まれるという不自然な状態が生まれています。

提案された解決策

Example関数がオプションでerrorを戻り値として返せるようにします。ianlancetaylor氏が早期に指摘したように、// Output:または// Unordered output:コメントを持つExample関数と同様に実行され、非nilのerrorが返された場合はテスト失敗と判定されます。
adonovan氏が2026年5月にまとめた最終仕様:

  • Example関数がオプションでerrorを返せるようにする
  • // Output:または// Unordered output:コメントを持つ場合はgo testで実行される(現在と同様)
  • 非nilのerrorが返された場合はExample失敗
  • nilが返された場合は出力を通常通り検証
  • Test関数やBenchmark関数への変更はなし

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

エラーを返す関数を呼び出すサンプルコードが、実際のアプリケーションコードに近い自然な形で書けるようになります。

コード例

// Before: panicやlog.Fatalを使った不自然な書き方
func ExampleWriteTempFile() {
    filename, cleanup, err := WriteTempFile("", "example-*", []byte("example"))
    if err != nil {
        log.Fatal(err) // deferが実行されない問題が生じる
    }
    defer cleanup(nil)
    fmt.Printf("Temporary example file name: %s\n", filename)
    // Output: Temporary example file name: /tmp/example-12345
}
// After: エラーをそのまま返せるシンプルな書き方
func ExampleWriteTempFile() error {
    filename, cleanup, err := WriteTempFile("", "example-*", []byte("example"))
    if err != nil {
        return err
    }
    defer cleanup(&err)
    fmt.Printf("Temporary example file name: %s\n", filename)
    // Output: Temporary example file name: /tmp/example-12345
}

実践的なメリットとして以下が挙げられます。

  1. ドキュメント品質の向上: GoDocやpkg.go.devで表示されるサンプルコードが実際のプロダクションコードのパターン(エラーをreturnで上位に委譲する)を正しく反映できる
  2. deferとの整合性: log.Fatalを使わずに済むため、deferを含む例が正しく動作するサンプルを書ける
  3. 学習コストの低減: Go初学者がサンプルコードを見て「なぜpanicしているのか」「なぜlog.Fatalなのか」と混乱しなくて済む

議論のハイライト

  • 2018年のhold: rsc氏がGo 2のエラーハンドリング構文改善を待つべきとしてholdに設定。しかしその後、エラーハンドリング構文改善自体が無期限延期となり(2025年)、holdの根拠が消滅した
  • return nilの必要性の懸念: dmitshur氏は最後にreturn nilが必須となる点を懸念したが、rogpeppe氏は「関数宣言も表示しないGodocと同様に、末尾のreturn nilも省略表示できる」と回答
  • 表示形式の課題: neild氏(2026年5月)がpkg.go.devでのコード表示への影響を指摘。_testパッケージ内のExample関数はfunc main()として表示されるが、errorを返す関数はそのままmain関数に変換できないため、表示方法の検討が必要
  • TestやBenchmarkへの拡張の議論: aclements氏は関連Issue #64993(TestにTestingT引数を許可する提案)との整合性に言及し、Testへのerrorリターン対応も将来的に考えられると述べた
  • ワークアラウンドの複雑さ: marco-m氏やその他の貢献者が現在の回避策(内部ヘルパー関数でラップする、panicする、log.Fatalを使う)の不自然さを具体的な例とともに示し、提案の必要性を裏付けた

関連リンク