Go模板无法在字段上调用方法

Go template can't call method on field

我有一个围绕 net/mail.Address 的包装器,它提供了一些编组逻辑。我试图在模板中使用它,但我一直收到 can't evaluate field String in type EmailAddress。模板文档说:

The name of a niladic method of the data, preceded by a period, such as

  .Method

The result is the value of invoking the method with dot as the receiver, dot.Method().

Method invocations may be chained and combined with fields and keys to any depth:

  .Field1.Key1.Method1.Field2.Key2.Method2

考虑到这一点,我写了这篇文章:

package main

import (
    "bytes"
    "fmt"
    "html/template"
    "net/mail"
    "os"
)

type EmailAddress struct{ mail.Address }

type emailFormatter struct {
    From         EmailAddress
    To           EmailAddress
}

var tmpl = template.Must(template.New("Sample Text").Parse("From: {{.From.String}}\r" + `
To: {{.To.String}}` + "\r" + `
Content-Type: text/html` + "\r" + `
Subject: Sample Text` + "\r\n\r" + `
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Sample Text</title>
    <meta charset="utf-8"/>
</head>
<body>
    Sample Text
</body>
</html>
`));

func main() {
    to := EmailAddress{
        mail.Address{
            Address: "em@i.l",
            Name: "",
        },
    }
    from := EmailAddress{
        mail.Address{
            Address: "no-reply@test.quest",
            Name: "",
        },
    }

    fmt.Println(to.String()) //outputs (as expected) "<em@i.l>"
    fmt.Println(from.String()) //outputs (as expected) "<no-reply@test.quest>"

    f := emailFormatter{
        To: to,
        From: from,
    }

    var buff bytes.Buffer
    if err := tmpl.Execute(&buff, f); err != nil {
        fmt.Printf("Error: %v\n", err)
        os.Exit(1)
    }
    fmt.Println(buff.String())
}

我已经单独验证调用EmailAddress.String是完全合法的,所以我不明白为什么只有这样的输出:

Error: template: Sample Text:1:13: executing "Sample Text" at <.From.String>: can't evaluate field String in type main.EmailAddress

编辑

根据评论者的建议,我将调用从 .From.String.To.String 更改为 .From.Address.String.To.Address.String,因为

"String isn't defined on EmailAddress, it's defined on EmailAddress.Address"

但结果是一样的:

Error: template: Sample Text:1:13: executing "Sample Text" at <.From.Address.String>: can't evaluate field String in type mail.Address

由于 String 是使用指针接收器定义的,因此您需要将 mail.Address 的 "addressable" 实例传递给模板才能执行该方法。

您可以通过传递指向 f 的指针来完成此操作。

if err := tmpl.Execute(&buff, &f); err != nil {
    panic(err)
}

或者您可以通过传递指向 EmailAddress.

的指针来实现
type emailFormatter struct {
    From *EmailAddress
    To   *EmailAddress
}

// ...

f := emailFormatter{
    To:   &to,
    From: &from,
}

// ...

if err := tmpl.Execute(&buff, f); err != nil {
    panic(err)
}

或者传入指向 mail.Address 的指针。

type EmailAddress struct{ *mail.Address }

// ...

to := EmailAddress{
    &mail.Address{
        Address: "em@i.l",
        Name: "",
    },
}
from := EmailAddress{
    &mail.Address{
        Address: "no-reply@test.quest",
        Name: "",
    },
}

f := emailFormatter{
    To:   to,
    From: from,
}

// ...

if err := tmpl.Execute(&buff, f); err != nil {
    panic(err)
}

请注意,您不需要在 Go 代码中执行此操作的原因是编译器会为您执行此操作。

例如:

fmt.Println(to.String())

变为:

fmt.Println((&to).String())

A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m()