Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 10690] ファイルの概要

このコミットは、Go言語の標準ライブラリである html/template パッケージにおける Must 関数の挙動を修正するものです。具体的には、Must 関数がエラーを受け取った際に適切にパニック(panic)を引き起こすように変更されています。

コミット

commit 5912869d61f0b20aaa9ea858195e8039ddc78dc6
Author: Rob Pike <r@golang.org>
Date:   Fri Dec 9 10:47:36 2011 -0800

    html/template: make Must work
    Fixes #2545.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5475054

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/5912869d61f0b20aaa9ea858195e8039ddc78dc6

元コミット内容

このコミットは、html/template パッケージ内の Must 関数が、引数として渡されたエラーが nil でない場合にパニックを引き起こすように修正しています。元の実装では、template.Must 関数を呼び出す際に、t.text というフィールドに対してのみ template.Must を適用しており、Must 関数自体に渡された err 引数を直接チェックしていませんでした。

変更の背景

この変更は、Go言語のIssue #2545を修正するために行われました。html/template パッケージの Must 関数は、text/template パッケージの Must 関数と同様に、テンプレートのパース時などに発生したエラーを捕捉し、そのエラーが nil でない場合にプログラムをパニックさせることを意図しています。これは、アプリケーションの起動時など、テンプレートの初期化が失敗した場合に、その後の処理を続行させずに即座に異常終了させることで、不正な状態での実行を防ぐための一般的なパターンです。

しかし、このコミット以前の html/templateMust 関数は、その意図された挙動を完全に満たしていませんでした。具体的には、Must 関数にエラーが渡されたとしても、それが t.text フィールドに関連するエラーでなければ、パニックが発生しない可能性がありました。この修正は、Must 関数が受け取ったエラーを直接評価し、エラーが存在すれば確実にパニックを引き起こすようにすることで、この不整合を解消し、関数の本来の目的を達成することを目的としています。

前提知識の解説

Go言語の template パッケージ

Go言語には、テキストベースのテンプレートを扱う text/template パッケージと、HTMLの安全性を考慮した html/template パッケージがあります。これらのパッケージは、データ構造とテンプレートを組み合わせて動的な出力を生成するために使用されます。

Must 関数

template パッケージ(text/template および html/template)には、Must というヘルパー関数が提供されています。この関数は、(*Template, error) のような戻り値を持つ関数(例: template.ParseFilestemplate.ParseGlob)の呼び出しをラップするために使用されます。Must 関数の主な目的は、テンプレートのパースや初期化の段階でエラーが発生した場合に、そのエラーが nil でなければ即座にプログラムをパニックさせることです。

これは、特にアプリケーションの起動時にグローバルなテンプレート変数を初期化する際によく用いられます。もしテンプレートのロードが正しく行えない場合、それはアプリケーションの実行を妨げる重大な設定エラーであると見なされるため、パニックによってプログラムを停止させることが適切と判断されます。

パニック (Panic) とリカバリ (Recover)

Go言語における「パニック」は、プログラムの実行を中断させるランタイムエラーの一種です。通常、回復不可能なエラーや、プログラムが続行できないような致命的な状況で発生します。パニックが発生すると、現在のゴルーチンは実行を停止し、遅延関数(defer)が実行され、その後呼び出し元の関数へとパニックが伝播していきます。最終的に、パニックがどこでもリカバリされなければ、プログラム全体がクラッシュします。

Must 関数がパニックを使用するのは、テンプレートの初期化エラーが「回復不可能」なエラーであるという設計思想に基づいています。

Issue #2545

Go言語のIssueトラッカーで報告された問題で、html/template パッケージの Must 関数が期待通りに動作しないというバグでした。このコミットはこのIssueを解決するために作成されました。

技術的詳細

このコミットの技術的な核心は、html/template パッケージの Must 関数が、text/template パッケージの Must 関数と同様の堅牢なエラーハンドリングロジックを持つようにすることです。

元の html/template.Must 関数は以下のようになっていました(コミット前の想定されるコード):

func Must(t *Template, err error) *Template {
    t.text = template.Must(t.text, err) // ここでtext/template.Mustを呼び出している
    return t
}

このコードでは、html/template.Template 構造体の text フィールド(これは text/template.Template 型)に対して text/template.Must を呼び出しています。text/template.Must は、その引数として渡された errnil でなければパニックを引き起こします。

しかし、この実装の問題点は、html/template.Must 関数に渡された err 引数が、t.text の初期化とは直接関係のないエラーであった場合、t.text が既に有効な状態であれば、text/template.Must がパニックを引き起こさない可能性があったことです。つまり、html/template.Must に渡された err が、t.text のパースエラーではなく、例えば html/template 独自の処理で発生したエラーだった場合、そのエラーが適切に処理されずに無視される可能性がありました。

このコミットによる修正は、この問題を解決するために、html/template.Must 関数が自身の err 引数を直接チェックするように変更しました。

修正後のコードは以下のようになります。

func Must(t *Template, err error) *Template {
    if err != nil { // ここで直接errをチェック
        panic(err)
    }
    return t
}

この変更により、html/template.Must 関数は、t.text の状態に関わらず、引数として渡された errnil でない限り、確実にパニックを引き起こすようになりました。これにより、html/templateMust 関数が、text/templateMust 関数と同様に、初期化時のエラーを確実に捕捉し、プログラムの異常終了を促すという意図された挙動を実現しています。

コアとなるコードの変更箇所

変更は src/pkg/html/template/template.go ファイルの Must 関数にあります。

--- a/src/pkg/html/template/template.go
+++ b/src/pkg/html/template/template.go
@@ -183,7 +183,9 @@ func (t *Template) Lookup(name string) *Template {
 
 // Must panics if err is non-nil in the same way as template.Must.
 func Must(t *Template, err error) *Template {
-	t.text = template.Must(t.text, err)
+	if err != nil {
+		panic(err)
+	}
 	return t
 }
 

コアとなるコードの解説

変更された Must 関数は以下のようになります。

func Must(t *Template, err error) *Template {
	if err != nil {
		panic(err)
	}
	return t
}

この修正は非常にシンプルですが、その影響は重要です。

  1. if err != nil: この行が追加され、Must 関数に渡された err 引数が nil でないかどうかを直接チェックします。
  2. panic(err): もし errnil でなければ、そのエラーを引数として panic 関数が呼び出されます。これにより、プログラムの実行が即座に中断され、エラー情報が伝播します。
  3. return t: errnil の場合(つまりエラーがない場合)のみ、引数として渡された *Template ポインタ t がそのまま返されます。

この変更により、html/template.Must は、text/template.Must と同様に、初期化時に発生したエラーを確実に捕捉し、プログラムをパニックさせるという、その名前と目的が示す通りの挙動をするようになりました。これにより、テンプレートの初期化が失敗した場合に、アプリケーションが不正な状態で起動することを防ぐことができます。

関連リンク

参考にした情報源リンク

  • GitHubのコミットページ: https://github.com/golang/go/commit/5912869d61f0b20aaa9ea858195e8039ddc78dc6
  • Web検索結果 (Google Search): golang html/template Must function Fixes #2545
    • reddit.com の議論 (Goのtemplate.Mustの意図に関する情報)
    • freshman.tech の記事 (テンプレート実行時のエラーハンドリングに関する情報)
  • Go言語の公式ドキュメント (GoDoc)
  • Go言語のソースコード (src/pkg/html/template/template.go)