模板中的零指针评估...为什么? 有更好的策略吗?

I am attempting to wrap html/template so I am guaranteed to have certain data in my templates (session data for example) in addition to the data I want to render. However, my current approach is...flawed. Here's a simplified example below:

package main

import "fmt"
import "os"
import "html/template"

func main() {
    // Passing nil directly to Execute doesn't render anything for missing struct fields
    fmt.Print("Directly rendering nil
")
    tmpl, err := template.New("master").Parse("Foo is: {{.Data.Foo}}")
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    err = tmpl.Execute(os.Stdout, nil)
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    // Wrapping templates works as long as I supply data...
    fmt.Print("
Rendering Foo
")
    render(struct {
        Foo string
    }{
        "foo",
    })

    // ...but this breaks.
    fmt.Print("
Rendering nil
")
    render(nil)
}

func render(data interface{}) {
    allData := struct {
        Session string
        Data    interface{}
    }{
        "sessionData",
        data,
    }

    // Hardcoded template for the example - this could be any arbitrary template
    tmpl, err := template.New("master").Parse("Foo is: {{.Data.Foo}}")
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    err = tmpl.Execute(os.Stdout, allData)
    if err != nil {
        fmt.Printf(err.Error())
        return
    }
}

I get the following output:

Directly rendering nil
Foo is: 
Rendering Foo
Foo is: foo
Rendering nil
Foo is: template: master:1:15: executing "master" at <.Data.Foo>: nil pointer evaluating interface {}.Foo

So I'm not quite sure what's going on in the first place - why is it that html/template is capable of handling being passed nil, but can't figure out what to do with a nil pointer?

Secondly, is there a better way of approaching this problem?

Your best bet is to always make Data a map or struct, either by making the type a map or struct, or by not using a nil with interface{}:

package main

import "fmt"
import "os"
import "text/template"

func main() {
    tmpl, err := template.New("master").Parse("{{if .Data.Foo}}Foo is: {{.Data.Foo}}{{else}}Foo is empty{{end}}")
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    err = tmpl.Execute(os.Stdout, nil)
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    fmt.Println("")

    err = tmpl.Execute(os.Stdout, struct {
        Session string
        Data    map[string]string
    }{
        "sessionData",
        nil,
    })
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    fmt.Println("")

    err = tmpl.Execute(os.Stdout, struct {
        Session string
        Data    interface{}
    }{
        "sessionData",
        map[string]string{},
    })
    if err != nil {
        fmt.Printf(err.Error())
        return
    }

    fmt.Println("")
}

Play: http://play.golang.org/p/9GkAp6ysvD

As for why it works like this, it's a bit complicated, you have to look at the code: https://golang.org/src/text/template/exec.go?s=4647:4717#L521

When execute is called with nil, reflect.ValueOf(nil) returns an invalid Value, so evalField returns the zero value, and you end up with an empty string.

However, when execute is called with a valid struct, that first reflect.ValueOf returns a valid value. The .Data command calls evalField on the whole struct you passed to Execute, and evalField calls FieldByIndex/FieldByName to get the "Data" field. This doesn't return an invalid Value.

Next, when .Foo is evaluated, if Data is an interface or a pointer, the indirect function follows it to the end, and if it finds that it's nil, it fails with this error.

When Data is a map, the indirect function doesn't do anything, and it doesn't fail.

This might be a bug in the text/template package.