[インデックス 12077] ファイルの概要
このコミットは、Go言語の標準ライブラリであるhtml/template
パッケージにおける重要な修正とリファクタリングを含んでいます。主な目的は、テンプレートエンジンが値を文字列化する際に、fmt.Stringer
インターフェースやerror
インターフェースを実装する型に対して、不必要にポインタをデリファレンスしすぎないようにすることです。これにより、これらのインターフェースが提供するカスタムの文字列表現が正しく尊重されるようになります。また、副次的な変更として、エスケープ処理に関連する内部関数の命名規則が整理され、exp_template_html_
プレフィックスがhtml_template_
に変更されています。
コミット
commit 0ce6c87004245fcbfe0747fa42b2a23d52890154
Author: Rob Pike <r@golang.org>
Date: Mon Feb 20 14:23:45 2012 +1100
html/template: don't indirect past a Stringer
While we're here, get rid of the old names for the escaping functions.
Fixes #3073.
R=golang-dev, dsymonds, rsc
CC=golang-dev
https://golang.org/cl/5685049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0ce6c87004245fcbfe0747fa42b2a23d52890154
元コミット内容
このコミットの元々の内容は以下の通りです。
html/template
:Stringer
を越えて間接参照しないようにする。- ついでに、エスケープ関数の古い名前を削除する。
- Issue #3073を修正。
変更の背景
Go言語のhtml/template
パッケージは、WebアプリケーションにおいてHTMLコンテンツを安全に生成するために設計されています。特に、クロスサイトスクリプティング(XSS)攻撃などのセキュリティ脆弱性を防ぐために、出力されるデータを自動的にエスケープする機能が組み込まれています。
このコミットが行われる前、html/template
パッケージ内部のstringify
関数は、テンプレートに渡された値を文字列に変換する際に、indirect
というヘルパー関数を使用してポインタをデリファレンスしていました。indirect
関数は、値がポインタである限り、そのポインタが指す実体へと再帰的にデリファレンスを繰り返す設計でした。
しかし、この挙動には問題がありました。Goにはfmt.Stringer
インターフェース(String() string
メソッドを持つ)やerror
インターフェース(Error() string
メソッドを持つ)が存在します。これらのインターフェースを実装する型は、自身の文字列表現をカスタムで定義できます。例えば、*MyStruct
がStringer
を実装している場合、indirect
が*MyStruct
をさらにデリファレンスしてMyStruct
の内部構造を直接参照しようとすると、String()
メソッドが提供する意図された文字列表現が無視されてしまう可能性がありました。これは、開発者が期待する出力と異なる結果を生み出し、特にデバッグ情報やエラーメッセージの表示において不都合でした。
この問題は、GoのIssue #3073として報告されました。報告されたシナリオでは、*bytes.Buffer
のようなStringer
を実装する型がテンプレートに渡された際に、期待されるString()
メソッドの出力ではなく、内部のバイト配列がそのまま表示されてしまうというものでした。これは、indirect
がStringer
インターフェースの実装を「見過ごして」しまい、さらにデリファレンスを進めてしまったために発生しました。
このコミットは、この問題を解決するために、indirect
関数の代わりにindirectToStringerOrError
という新しい関数を導入し、Stringer
またはerror
インターフェースに到達した時点でデリファレンスを停止するように変更しました。
また、このコミットには、エスケープ関数の命名規則の整理という副次的な変更も含まれています。以前はexp_template_html_
というプレフィックスが使われていましたが、これはおそらく「experimental(実験的)」を意味しており、機能が安定したため、より簡潔なhtml_template_
に変更されたと考えられます。これはコードベースのクリーンアップと整合性の向上を目的としたものです。
前提知識の解説
Go言語のhtml/template
パッケージ
html/template
パッケージは、Go言語でWebアプリケーションを開発する際に、HTML出力を安全に生成するためのテンプレートエンジンです。このパッケージの主要な目的は、ユーザー入力や外部データを含むコンテンツをHTMLに埋め込む際に発生しうるクロスサイトスクリプティング(XSS)攻撃を防ぐことです。
- 自動エスケープ:
html/template
は、出力されるコンテキスト(HTML要素のテキスト、属性値、JavaScriptコード、CSSなど)に応じて、適切なエスケープ処理を自動的に適用します。これにより、悪意のあるスクリプトが挿入されるのを防ぎます。 - コンテキストアウェアネス: テンプレートエンジンは、現在処理しているHTMLのどの部分(例:
<div>
タグの中、href
属性の中、<script>
タグの中)であるかを認識し、そのコンテキストに最適なエスケープ戦略を選択します。
Go言語のfmt.Stringer
インターフェースとerror
インターフェース
Go言語では、インターフェースは型が満たすべき振る舞いを定義します。
-
fmt.Stringer
インターフェース:type Stringer interface { String() string }
このインターフェースは、
String() string
というメソッドを1つだけ持ちます。任意の型がこのメソッドを実装すると、その型はfmt.Stringer
インターフェースを満たします。fmt.Print
やfmt.Sprintf
などのfmt
パッケージの関数は、引数がStringer
インターフェースを実装している場合、そのString()
メソッドを呼び出して文字列表現を取得します。これにより、開発者はカスタムデータ型の文字列化の挙動を制御できます。 -
error
インターフェース:type error interface { Error() string }
Go言語におけるエラー処理の基本となるインターフェースです。
Error() string
メソッドを実装することで、エラーの詳細な文字列表現を提供します。fmt.Print
などもerror
インターフェースを実装する型に対してはError()
メソッドを呼び出します。
Go言語のリフレクション(reflect
パッケージ)
reflect
パッケージは、Goプログラムが実行時に自身の構造を検査(リフレクト)したり、変更したりするための機能を提供します。
reflect.ValueOf(interface{})
: 任意のGoの値をreflect.Value
型に変換します。reflect.Value
は、その値の型や内容に関する情報を提供します。reflect.Type
: Goの型の情報を表します。reflect.ValueOf().Type()
で値の型情報を取得できます。Type.Implements(interfaceType reflect.Type)
: あるreflect.Type
が、指定されたインターフェースのreflect.Type
を実装しているかどうかをチェックします。この機能は、実行時に特定のインターフェースの実装を動的に確認するために使用されます。Value.Kind()
:reflect.Value
が表す値の基本的な種類(例:reflect.Ptr
、reflect.Struct
、reflect.Int
など)を返します。Value.Elem()
: ポインタの場合、そのポインタが指す要素のreflect.Value
を返します。インターフェースの場合、インターフェースが保持する実体のreflect.Value
を返します。Value.IsNil()
:reflect.Value
がnilであるかどうかをチェックします。
ポインタのデリファレンス
ポインタは、メモリ上の特定のアドレスを指す変数です。ポインタのデリファレンスとは、ポインタが指すアドレスに格納されている実際の値を取得する操作です。Goでは、*
演算子を使ってデリファレンスを行います(例: *ptr
)。リフレクションでは、reflect.Value.Elem()
メソッドがこのデリファレンスに相当する操作を行います。
技術的詳細
このコミットの技術的な変更は、主にsrc/pkg/html/template/content.go
とsrc/pkg/html/template/escape.go
の2つのファイルに集中しています。
src/pkg/html/template/content.go
の変更
-
新しい型変数の追加:
errorType
とfmtStringerType
という2つのreflect.Type
型の変数が追加されました。これらは、それぞれerror
インターフェースとfmt.Stringer
インターフェースの型情報を事前に取得し、reflect.Type.Implements
メソッドでの比較を効率化するために使用されます。var ( errorType = reflect.TypeOf((*error)(nil)).Elem() fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() )
(*error)(nil)).Elem()
のようにすることで、error
インターフェース自体のreflect.Type
を取得しています。これは、reflect.TypeOf(nil)
がnil
を返すため、一度ポインタ型としてnil
を生成し、その要素の型を取得するというイディオムです。 -
indirectToStringerOrError
関数の追加: この新しい関数は、既存のindirect
関数の改良版です。func indirectToStringerOrError(a interface{}) interface{} { v := reflect.ValueOf(a) for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() { v = v.Elem() } return v.Interface() }
この関数は、引数
a
のreflect.Value
を取得し、以下の条件がすべて真である限り、ポインタのデリファレンス(v = v.Elem()
)を繰り返します。v
の型がfmt.Stringer
インターフェースを実装していない (!v.Type().Implements(fmtStringerType)
)v
の型がerror
インターフェースを実装していない (!v.Type().Implements(errorType)
)v
がポインタ型である (v.Kind() == reflect.Ptr
)v
がnilポインタではない (!v.IsNil()
)
これにより、値が
fmt.Stringer
またはerror
インターフェースを実装している場合、そのインターフェースの実体までデリファレンスが進んだ時点でループが停止し、それ以上内部構造に踏み込むことがなくなります。最終的に、デリファレンスされた(またはデリファレンスされなかった)値のインターフェース表現が返されます。 -
stringify
関数の変更:stringify
関数は、テンプレートに渡された複数の引数を文字列に変換する役割を担っています。この関数内で、各引数に対してindirect
関数が呼び出されていましたが、これが新しく追加されたindirectToStringerOrError
関数に置き換えられました。// 変更前 // args[i] = indirect(arg) // 変更後 args[i] = indirectToStringerOrError(arg)
この変更により、
stringify
が値を処理する際に、fmt.Stringer
やerror
のカスタム文字列化ロジックが尊重されるようになりました。
src/pkg/html/template/content_test.go
の変更
このファイルには、indirectToStringerOrError
関数の新しい挙動を検証するためのTestStringer
というテスト関数が追加されました。
stringer
構造体とerrorer
構造体が定義され、それぞれfmt.Stringer
とerror
インターフェースを実装しています。- これらの構造体のポインタがテンプレートに渡された際に、
String()
またはError()
メソッドが正しく呼び出され、期待される文字列が出力されることを確認しています。これは、Issue #3073で報告された問題が解決されたことを直接的に検証するものです。
src/pkg/html/template/escape.go
の変更
このファイルでは、funcMap
とequivEscapers
という2つのマップのキー名が変更されました。
funcMap
: テンプレート内で使用されるエスケープ関数をマッピングする変数です。equivEscapers
: エスケープ関数の冗長性を定義するマップです。
これらのマップ内で使用されていたキー名が、exp_template_html_
プレフィックスからhtml_template_
プレフィックスへと一括で変更されました。例えば、"exp_template_html_attrescaper"
は"html_template_attrescaper"
になりました。
同様に、escapeAction
関数内のappend(s, ...)
の呼び出し箇所でも、これらの新しい関数名が使用されるように変更されています。
この変更は機能的なものではなく、命名規則の整理とコードベースのクリーンアップを目的としたものです。exp_
(experimental)というプレフィックスが削除されたことから、これらのエスケープ機能がもはや実験的なものではなく、安定した機能として扱われるようになったことを示唆しています。
コアとなるコードの変更箇所
src/pkg/html/template/content.go
--- a/src/pkg/html/template/content.go
+++ b/src/pkg/html/template/content.go
@@ -85,6 +85,22 @@ func indirect(a interface{}) interface{} {
return v.Interface()
}
+var (
+ errorType = reflect.TypeOf((*error)(nil)).Elem()
+ fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+)
+
+// indirectToStringerOrError returns the value, after dereferencing as many times
+// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer
+// or error,
+func indirectToStringerOrError(a interface{}) interface{} {
+ v := reflect.ValueOf(a)
+ for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() {
+ v = v.Elem()
+ }
+ return v.Interface()
+}
+
// stringify converts its arguments to a string and the type of the content.
// All pointers are dereferenced, as in the text/template package.
func stringify(args ...interface{}) (string, contentType) {
@@ -107,7 +123,7 @@ func stringify(args ...interface{}) (string, contentType) {
}
}
for i, arg := range args {
- args[i] = indirect(arg)
+ args[i] = indirectToStringerOrError(arg)
}
return fmt.Sprint(args...), contentTypePlain
}
src/pkg/html/template/escape.go
--- a/src/pkg/html/template/escape.go
+++ b/src/pkg/html/template/escape.go
@@ -46,30 +46,30 @@ func escapeTemplates(tmpl *Template, names ...string) error {\n
// funcMap maps command names to functions that render their inputs safe.\n var funcMap = template.FuncMap{\n-\t"exp_template_html_attrescaper": attrEscaper,\n-\t"exp_template_html_commentescaper": commentEscaper,\n-\t"exp_template_html_cssescaper": cssEscaper,\n-\t"exp_template_html_cssvaluefilter": cssValueFilter,\n-\t"exp_template_html_htmlnamefilter": htmlNameFilter,\n-\t"exp_template_html_htmlescaper": htmlEscaper,\n-\t"exp_template_html_jsregexpescaper": jsRegexpEscaper,\n-\t"exp_template_html_jsstrescaper": jsStrEscaper,\n-\t"exp_template_html_jsvalescaper": jsValEscaper,\n-\t"exp_template_html_nospaceescaper": htmlNospaceEscaper,\n-\t"exp_template_html_rcdataescaper": rcdataEscaper,\n-\t"exp_template_html_urlescaper": urlEscaper,\n-\t"exp_template_html_urlfilter": urlFilter,\n-\t"exp_template_html_urlnormalizer": urlNormalizer,\n+\t"html_template_attrescaper": attrEscaper,\n+\t"html_template_commentescaper": commentEscaper,\n+\t"html_template_cssescaper": cssEscaper,\n+\t"html_template_cssvaluefilter": cssValueFilter,\n+\t"html_template_htmlnamefilter": htmlNameFilter,\n+\t"html_template_htmlescaper": htmlEscaper,\n+\t"html_template_jsregexpescaper": jsRegexpEscaper,\n+\t"html_template_jsstrescaper": jsStrEscaper,\n+\t"html_template_jsvalescaper": jsValEscaper,\n+\t"html_template_nospaceescaper": htmlNospaceEscaper,\n+\t"html_template_rcdataescaper": rcdataEscaper,\n+\t"html_template_urlescaper": urlEscaper,\n+\t"html_template_urlfilter": urlFilter,\n+\t"html_template_urlnormalizer": urlNormalizer,\n }\n \n // equivEscapers matches contextual escapers to equivalent template builtins.\n var equivEscapers = map[string]string{\n-\t"exp_template_html_attrescaper": "html",\n-\t"exp_template_html_htmlescaper": "html",\n-\t"exp_template_html_nospaceescaper": "html",\n-\t"exp_template_html_rcdataescaper": "html",\n-\t"exp_template_html_urlescaper": "urlquery",\n-\t"exp_template_html_urlnormalizer": "urlquery",\n+\t"html_template_attrescaper": "html",\n+\t"html_template_htmlescaper": "html",\n+\t"html_template_nospaceescaper": "html",\n+\t"html_template_rcdataescaper": "html",\n+\t"html_template_urlescaper": "urlquery",\n+\t"html_template_urlnormalizer": "urlquery",\n }\n \n // escaper collects type inferences about templates and changes needed to make\n@@ -147,17 +147,17 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {\n \tcase stateURL, stateCSSDqStr, stateCSSSqStr, stateCSSDqURL, stateCSSSqURL, stateCSSURL:\n \t\tswitch c.urlPart {\n \t\tcase urlPartNone:\n-\t\t\ts = append(s, "exp_template_html_urlfilter")\n+\t\t\ts = append(s, "html_template_urlfilter")\n \t\t\tfallthrough\n \t\tcase urlPartPreQuery:\n \t\t\tswitch c.state {\n \t\t\tcase stateCSSDqStr, stateCSSSqStr:\n-\t\t\t\ts = append(s, "exp_template_html_cssescaper")\n+\t\t\t\ts = append(s, "html_template_cssescaper")\n \t\t\tdefault:\n-\t\t\t\ts = append(s, "exp_template_html_urlnormalizer")\n+\t\t\t\ts = append(s, "html_template_urlnormalizer")\n \t\t\t}\n \t\tcase urlPartQueryOrFrag:\n-\t\t\ts = append(s, "exp_template_html_urlescaper")\n+\t\t\ts = append(s, "html_template_urlescaper")\n \t\tcase urlPartUnknown:\n \t\t\treturn context{\n \t\t\t\tstate: stateError,\n@@ -167,27 +167,27 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {\n \t\t\tpanic(c.urlPart.String())\n \t\t}\n \tcase stateJS:\n-\t\ts = append(s, "exp_template_html_jsvalescaper")\n+\t\ts = append(s, "html_template_jsvalescaper")\n \t\t// A slash after a value starts a div operator.\n \t\tc.jsCtx = jsCtxDivOp\n \tcase stateJSDqStr, stateJSSqStr:\n-\t\ts = append(s, "exp_template_html_jsstrescaper")\n+\t\ts = append(s, "html_template_jsstrescaper")\n \tcase stateJSRegexp:\n-\t\ts = append(s, "exp_template_html_jsregexpescaper")\n+\t\ts = append(s, "html_template_jsregexpescaper")\n \tcase stateCSS:\n-\t\ts = append(s, "exp_template_html_cssvaluefilter")\n+\t\ts = append(s, "html_template_cssvaluefilter")\n \tcase stateText:\n-\t\ts = append(s, "exp_template_html_htmlescaper")\n+\t\ts = append(s, "html_template_htmlescaper")\n \tcase stateRCDATA:\n-\t\ts = append(s, "exp_template_html_rcdataescaper")\n+\t\ts = append(s, "html_template_rcdataescaper")\n \tcase stateAttr:\n \t\t// Handled below in delim check.\n \tcase stateAttrName, stateTag:\n \t\tc.state = stateAttrName\n-\t\ts = append(s, "exp_template_html_htmlnamefilter")\n+\t\ts = append(s, "html_template_htmlnamefilter")\n \tdefault:\n \t\tif isComment(c.state) {\n-\t\t\ts = append(s, "exp_template_html_commentescaper")\n+\t\t\ts = append(s, "html_template_commentescaper")\n \t\t} else {\n \t\t\tpanic("unexpected state " + c.state.String())\n \t\t}\n@@ -196,9 +196,9 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {\n \tcase delimNone:\n \t\t// No extra-escaping needed for raw text content.\n \tcase delimSpaceOrTagEnd:\n-\t\ts = append(s, "exp_template_html_nospaceescaper")\n+\t\ts = append(s, "html_template_nospaceescaper")\n \tdefault:\n-\t\ts = append(s, "exp_template_html_attrescaper")\n+\t\ts = append(s, "html_template_attrescaper")\n \t}\n \te.editActionNode(n, s)\n \treturn c\n@@ -260,22 +260,22 @@ func ensurePipelineContains(p *parse.PipeNode, s []string) {\n // redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)\n // for all x.\n var redundantFuncs = map[string]map[string]bool{\n-\t"exp_template_html_commentescaper": {\n-\t\t"exp_template_html_attrescaper": true,\n-\t\t"exp_template_html_nospaceescaper": true,\n-\t\t"exp_template_html_htmlescaper": true,\n+\t"html_template_commentescaper": {\n+\t\t"html_template_attrescaper": true,\n+\t\t"html_template_nospaceescaper": true,\n+\t\t"html_template_htmlescaper": true,\n \t},\n-\t"exp_template_html_cssescaper": {\n-\t\t"exp_template_html_attrescaper": true,\n+\t"html_template_cssescaper": {\n+\t\t"html_template_attrescaper": true,\n \t},\n-\t"exp_template_html_jsregexpescaper": {\n-\t\t"exp_template_html_attrescaper": true,\n+\t"html_template_jsregexpescaper": {\n+\t\t"html_template_attrescaper": true,\n \t},\n-\t"exp_template_html_jsstrescaper": {\n-\t\t"exp_template_html_attrescaper": true,\n+\t"html_template_jsstrescaper": {\n+\t\t"html_template_attrescaper": true,\n \t},\n-\t"exp_template_html_urlescaper": {\n-\t\t"exp_template_html_urlnormalizer": true,\n+\t"html_template_urlescaper": {\n+\t\t"html_template_urlnormalizer": true,\n \t},\n }\n \n```
## コアとなるコードの解説
### `indirectToStringerOrError`関数の詳細
この関数は、Goのリフレクション機能を活用して、与えられた値がポインタである場合にそのポインタをデリファレンスし続けます。ただし、デリファレンス中に値が`fmt.Stringer`インターフェースまたは`error`インターフェースを実装していることが判明した場合、そこでデリファレンスを停止します。
```go
func indirectToStringerOrError(a interface{}) interface{} {
v := reflect.ValueOf(a) // (1) 引数aのreflect.Valueを取得
// (2) 以下の条件がすべて真である限りループを続ける:
// - vの型がfmt.Stringerを実装していない
// - vの型がerrorを実装していない
// - vがポインタ型である
// - vがnilポインタではない
for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem() // (3) ポインタをデリファレンスし、その要素のreflect.Valueをvに再代入
}
return v.Interface() // (4) 最終的なreflect.Valueをinterface{}型に戻して返す
}
reflect.ValueOf(a)
: 任意のinterface{}
型の引数a
から、その値のreflect.Value
表現を取得します。これにより、実行時に値の型や内容を検査・操作できるようになります。for
ループの条件:!v.Type().Implements(fmtStringerType)
: 現在のv
が表す型がfmt.Stringer
インターフェースを実装していないことを確認します。もし実装していれば、ループは終了します。!v.Type().Implements(errorType)
: 同様に、error
インターフェースを実装していないことを確認します。v.Kind() == reflect.Ptr
: 現在のv
がポインタ型であることを確認します。ポインタでなければ、これ以上デリファレンスする意味がないため、ループは終了します。!v.IsNil()
: 現在のv
がnilポインタではないことを確認します。nilポインタをデリファレンスしようとするとパニックが発生するため、これを防ぎます。
v = v.Elem()
: ループの条件が満たされている場合、v
がポインタであるため、Elem()
メソッドを呼び出してそのポインタが指す実体(要素)のreflect.Value
を取得し、v
を更新します。これにより、次のイテレーションではデリファレンスされた値が検査されます。return v.Interface()
: ループが終了した時点で、v
はfmt.Stringer
またはerror
を実装する型(または非ポインタ型、あるいはnil)を表しています。このreflect.Value
を元のinterface{}
型に戻して返します。
この関数により、html/template
は、String()
やError()
メソッドを持つ型に対しては、そのカスタム文字列化ロジックを尊重し、それ以上内部に踏み込まないようになりました。
stringify
関数における変更の影響
stringify
関数は、テンプレートの実行時に、表示されるべき値を最終的な文字列に変換する役割を担っています。以前は、この関数が各引数に対して無条件にindirect
を呼び出していました。
// 変更前:
// for i, arg := range args {
// args[i] = indirect(arg) // Stringerやerrorを無視してデリファレンスしすぎることがあった
// }
// 変更後:
for i, arg := range args {
args[i] = indirectToStringerOrError(arg) // Stringerやerrorを尊重してデリファレンスを停止
}
この変更により、stringify
がindirectToStringerOrError
を使用するようになったため、fmt.Stringer
やerror
を実装する型がテンプレートに渡された場合、それらのString()
またはError()
メソッドが期待通りに呼び出され、その結果がテンプレート出力に反映されるようになりました。これにより、Issue #3073で報告されたような、*bytes.Buffer
が期待通りに文字列化されない問題が解決されます。
エスケープ関数の名称変更の意図
src/pkg/html/template/escape.go
におけるエスケープ関数の名称変更は、主にコードの保守性と可読性の向上を目的としています。
exp_template_html_
からhtml_template_
へ:exp_
というプレフィックスは、"experimental"(実験的)の略であると推測されます。このプレフィックスが削除されたことは、これらのエスケープ機能がGoのhtml/template
パッケージの安定した、確立された一部として認識されるようになったことを示しています。- 内部的な整合性:
funcMap
やequivEscapers
、そしてescapeAction
関数内で参照されるこれらの関数名が一貫して変更されたことで、コードベース全体の命名規則が統一され、将来的な開発やメンテナンスが容易になります。この変更は、外部からhtml/template
パッケージを利用するユーザーには直接的な影響を与えませんが、パッケージの内部構造を理解する上でより明確になります。
これらの変更は、html/template
パッケージが成熟し、その内部実装がより洗練されたことを示しています。
関連リンク
- Go Gerrit Change-ID: https://golang.org/cl/5685049
参考にした情報源リンク
- Go Issue #3073: https://github.com/golang/go/issues/3073
- Go
fmt.Stringer
documentation: https://pkg.go.dev/fmt#Stringer - Go
reflect
package documentation: https://pkg.go.dev/reflect - Go
html/template
package documentation: https://pkg.go.dev/html/template