Go语言的text/template和html/template包自1.11版本起支持对已声明变量重新赋值,但必须使用空格分隔的$var = value语法,而非$var:=value(后者仅用于声明),否则赋值无效且无报错。
go语言的`text/template`和`html/template`包自1.11版本起支持对已声明变量重新赋值,但必须使用空格分隔的`$var = value`语法,而非`$var:=value`(后者仅用于声明),否则赋值无效且无报错。
在Go模板中,变量具有词法作用域(lexical scope),其生命周期和可见性由定义位置及嵌套结构严格决定。关键规则如下:
- 使用 {{$x := value}} 语法声明新变量,作用域为当前模板动作块(如 {{range}}、{{with}} 内部),不可跨作用域修改;
- 自 Go 1.11 起,模板支持对同级作用域内已声明的变量进行重新赋值,但语法必须是 {{$x = new_value}}(等号两侧需有空格),而非声明式 :={;
- {{with}}、{{range}} 等控制结构会创建新的作用域,其内部声明的变量对外不可见;若需在循环中维护状态(如分组标记),变量必须在最外层作用域声明,并在循环内用赋值语法更新。
以下为修复后的完整示例,实现按年份分组输出赛事:
package main
import (
"fmt"
"os"
"text/template"
"time"
)
func main() {
tournaments := []struct {
Place string
Date time.Time
}{
{"Town1", time.Date(2015, time.November, 10, 23, 0, 0, 0, time.Local)},
{"Town2", time.Date(2015, time.October, 10, 23, 0, 0, 0, time.Local)},
{"Town3", time.Date(2014, time.November, 10, 23, 0, 0, 0, time.Local)},
}
t, err := template.New("").Parse(`
{{$prev_year := 0}}
{{range .}}
{{with .Date}}
{{$year := .Year}}
{{if ne $year $prev_year}}
Actions in year {{$year}}:
{{$prev_year = $year}} <!-- ✅ 正确:赋值语法,空格必需 -->
{{end}}
{{end}}
{{.Place}}, {{.Date.Format "2006-01-02"}}
{{end}}
`)
if err != nil {
panic(err)
}
if err := t.Execute(os.Stdout, tournaments); err != nil {
fmt.Println("executing template:", err)
}
}输出结果:
Actions in year 2015: Town1, 2015-11-10 Town2, 2015-10-10 Actions in year 2014: Town3, 2014-11-10
⚠️ 注意事项:
- 模板中所有变量名以 $ 开头,属于模板命名空间,与Go代码变量完全隔离;
- {{$x := y}} 和 {{$x = y}} 语义不同:前者是声明+初始化(作用域内首次出现),后者是赋值(要求 $x 已在当前作用域声明);
- 若在 {{range}} 内部误用 {{$prev_year := .Year}},则每次迭代都会创建新变量,无法维持状态——这正是原代码失效的根本原因;
- 模板不支持传统编程中的“全局变量”,跨作用域通信需依赖数据预处理(如在Go代码中构造分组结构)或谨慎使用顶层作用域变量。
总结:Go模板变量是不可变作用域模型下的有限可变扩展。掌握 :=(声明)与 =(赋值)的语义差异,是编写逻辑清晰、状态可控模板的关键。


















