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

goを1.7に上げてから、gocodeが動かなくなったりもしました

go1.6.2 -> go1.7でgocodeが動作しない

手元のmacの環境のgoを1.7に上げたところ、gocodeが上手く動作しなくなった。 (macportsを使っている)

$ sudo port selfupdate
$ sudo port upgrade go
$ go version
go version go1.7 darwin/amd64

以下の様な感じの出力を返す様になってしまった。

$ cat xxx.go | gocode -debug autocomplete /tmp/xxx/xxx.go c89
Found 1 candidates:
  PANIC PANIC PANIC

対応

以下を参考にして、gocodeをcloseした後、インストールしなおしたら直った。

$ gocode close
$ go get -u github.com/nsf/gocode

上手く動作している。

$ cat xxx.go | gocode -debug  autocomplete /tmp/xxx/xxx.go c89
Found 7 candidates:
  func Command(name string, arg ...string) *exec.Cmd
  func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd
  func LookPath(file string) (string, error)
  type Cmd struct
  type Error struct
  type ExitError struct
  var ErrNotFound error

memo

/tmp/xxx/xxx.go

package main

import (
    "os/exec"
)

// cancel by context

func main(){
    cmd := exec.//|ここにカーソル
}