根本区别在于自动转义行为:html/template默认对插值做HTML实体转义,text/template完全不转义;渲染HTML必须用html/template,纯文本场景用text/template。
根本区别不在语法,而在自动转义行为:html/template 默认对所有 .Value 插值做 HTML 实体转义(如 → zuojiankuohaophpcn),text/template 完全不转义。选错会导致 XSS 漏洞或页面显示乱码。

text/template
html/template
template.ParseFiles、tmpl.Execute 等调用方式完全相同直接写 {{.HTMLContent}} 会被转义成一堆 zuojiankuohaophpcndivyoujiankuohaophpcn —— 这不是 bug,是默认防护。要绕过转义,必须显式声明该值“已可信”:
func main() {
tmpl := template.Must(template.New("").Parse(`{{.SafeHTML}}`))
data := struct{ SafeHTML template.HTML }{
SafeHTML: template.HTML(`hello`),
}
tmpl.Execute(os.Stdout, data)
}
template.HTML 类型包裹字符串,不能用 string 强转template.JS、template.CSS、template.URL 同理,用于对应上下文的安全注入template.HTML,这等于主动关闭 XSS 防护两者都支持自定义函数和接收者方法,但函数注册逻辑一致,而方法调用受接收者类型限制:
func sayHi(name string) string { return "Hi, " + name }
tmpl := template.Must(template.New("t").Funcs(template.FuncMap{"hi": sayHi}))
tmpl.Parse(`{{hi .Name}}`) // ✅ 正确
.),否则 Funcs 会 panichtml/template 中,函数返回值若为 string 仍会被转义;只有返回 template.HTML 等特殊类型才跳过转义多因模板语法写错,比如漏掉空格、括号不匹配,或在 {{if}} 条件里用了非法表达式:
{{if .User.Name == ""}} ✅ 合法(Go 模板支持简单比较){{if .User.Name == nil}} ❌ panic(nil 不可比较,改用 {{if not .User.Name}}){{range .Items}}{{.Name}{{end}} ❌ 缺少右括号,报错位置常指向开头的 .
template.Must 包裹 Parse,让错误在加载时暴露,而非执行时静默失败最易被忽略的是:同一个 *template.Template 实例不能并发调用 Execute,必须为每次请求克隆一份(tmpl.Clone())或创建新实例——否则可能 panic 或输出错乱。