読者です 読者をやめる 読者になる 読者になる

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.NameUser.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()
    }
}