goのtext/templateでprivateなattributeを出力しようとするとエラーになるという話
goのtext/templateでprivateなattributeを出力しようとすると、エラーになるという話を知らなかった。
全部public
例えば、以下はOK
type User struct { Name string Message string } type V map[string]interface{} func main() { const tmpl = `{{.user.Name}}: {{.user.Message}}` tpl := template.Must(template.New("mytemplate").Parse(tmpl)) user := User{Name: "foo", Message: "hello world"} if err := tpl.Execute(os.Stdout, V{"user": user}); err != nil { fmt.Println(err) } }
一部privateに
これを以下の様にする
@@ -7,19 +7,20 @@ ) type User struct { - Name string + name string Message string } type V map[string]interface{} func main() { - const tmpl = `{{.user.Name}}: {{.user.Message}}` + const tmpl = `{{.user.name}}: {{.user.Message}}` tpl := template.Must(template.New("mytemplate").Parse(tmpl)) - user := User{Name: "foo", Message: "hello world"} + user := User{name: "foo", Message: "hello world"}
User.Name
を User.name
にした。
するとエラーになる。
$ go run xxx.go template: mytemplate:1:7: executing "mytemplate" at <.user.name>: name is an unexported field of struct type interface {}
実際のところドキュメントに書いてあった
template - The Go Programming Language
Template is the representation of a parsed template. The *parse.Tree field is exported only for use by html/template and should be treated as unexported by all other clie
exported/unexportedチェックしているところ
単純に、template部分は別パッケージという扱いで非公開のメンバーにアクセスしようとしたからエラーということっぽい。 reflectionを使っているしruntime時にしかわからないのでruntime error。
text/template/exec.go の以下の部分で
func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value { // snip... switch receiver.Kind() { case reflect.Struct: tField, ok := receiver.Type().FieldByName(fieldName) if ok { // snip... if tField.PkgPath != "" { // field is unexported s.errorf("%s is an unexported field of struct type %s", fieldName, typ) }
tFieldは StructField型
reflect/type.go
// A StructField describes a single field in a struct. type StructField struct { // Name is the field name. Name string // PkgPath is the package path that qualifies a lower case (unexported) // field name. It is empty for upper case (exported) field names. // See https://golang.org/ref/spec#Uniqueness_of_identifiers PkgPath string Type Type // field type Tag StructTag // field tag string Offset uintptr // offset within struct, in bytes Index []int // index sequence for Type.FieldByIndex Anonymous bool // is an embedded field }
reflect/type.go のあたりでexportされていないfieldに対してはpkgPathを設定しているという感じになっているので。確かにexportedかどうかの判別に使えそう。
func (t *structType) Field(i int) (f StructField) { // snip.. if !p.name.isExported() { // Fields never have an import path in their name. f.PkgPath = t.pkgPath.name() } }