8

Playground link: http://play.golang.org/p/Ebf5AuJlcP

type Foo interface {}

type Bar interface {
    ThisIsABar()
}

// Person implements both Foo and Bar
type Person struct {
    Name string
}

func (p Person) ThisIsABar() {}

type FooContext struct {
    Something   Foo
}

type BarContext struct {
    Something   Bar
}

func main() {
    t := template.Must(template.New("test").Parse("{{ .Something.Name }}\n"))

    // This works fine.
    if err := t.Execute(os.Stdout, FooContext{Person{"Timmy"}}); err != nil {
        fmt.Printf("Error: %s\n", err)
    }

    // With BarContext, containing the exact same Person but wrapped in a
    // different interface, it errors out.
    if err := t.Execute(os.Stdout, BarContext{Person{"Timmy"}}); err != nil {
        fmt.Printf("Error: %s\n", err)
    }
}

When I render a template (via the text/template package) containing {{ .Something.Name }}, I can go through interface Foo which contains no methods, and it works fine. But if I go through interface Bar instead, I get:

executing "test" at <.Something.Name>: can't evaluate field Name in type main.Bar

Why does the presence of an unrelated method on the interface, that isn't even used, affect the rendering of the template?

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • 1
    possible duplicate of [template won't evaluate fields that are interface type as the underlying type](http://stackoverflow.com/questions/19554209/template-wont-evaluate-fields-that-are-interface-type-as-the-underlying-type) – nemo Oct 31 '13 at 16:33

1 Answers1

6

text/template is special casing interface{}, so called functions can have return type interface{}, etc. Adding a method to your interface means that detection no longer triggers.

http://golang.org/src/pkg/text/template/exec.go#L323

323     for _, cmd := range pipe.Cmds {
324         value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
325         // If the object has type interface{}, dig down one level to the thing inside.
326         if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
327             value = reflect.ValueOf(value.Interface()) // lovely!
328         }
329     }

BarContext.Something is a Bar (an interface). A Bar has no field Name. If you want to use an interface there, you'll need to provide the data via a method that is part of the interface.

Tv_
  • 163
  • 1
  • 3
  • Did you verify that this code is responsible for the behaviour? I think it is rather to do the implementation of [`indirect`](http://stackoverflow.com/a/19555267/1643939). But maybe I missed something. – nemo Oct 31 '13 at 16:35
  • Wow, that's unexpected. I somehow didn't find http://stackoverflow.com/questions/19554209/template-wont-evaluate-fields-that-are-interface-type-as-the-underlying-type but ended up with the same solution: just use `interface{}` when passing things to the template. – Thomas Oct 31 '13 at 18:25