ᕕ( ᐛ )ᕗ Jimyag's Blog

Go 1.23 新特性解析:函数迭代器与遥测

· 393 words · ~ 2 min read

Last modified:

Go 1.23 发布于 2024-08-13。它把 Go 1.22 预览的 range-over-function 变成正式语言能力,让标准库和业务代码可以用统一的迭代器约定表达惰性序列。

源码侧 api/go1.23.txt 有 158 条公开 API 增量;公开标准库目录新增 structsunique

主要变化

1. for range 支持函数迭代器

Go 1.23 允许 range 表达式是特定签名的函数。迭代器函数通过调用 yield 函数产出值,调用方仍然写 for v := range seq

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func Lines(r io.Reader) iter.Seq[string] {
    return func(yield func(string) bool) {
        scanner := bufio.NewScanner(r)
        for scanner.Scan() {
            if !yield(scanner.Text()) {
                return
            }
        }
    }
}

for line := range Lines(r) {
    fmt.Println(line)
}

这个能力解决的是“想流式遍历,但又想保留 range 语法”的问题。树遍历、分页查询、过滤管道、按需生成序列都可以不用先构造完整 slice。yield 返回 false 表示调用方停止迭代,迭代器应尽快返回。

双值迭代器用 iter.Seq2[K, V] 表达,适合 map、索引和值、键和值这类场景:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func Pairs(m map[string]int) iter.Seq2[string, int] {
    return func(yield func(string, int) bool) {
        for k, v := range m {
            if !yield(k, v) {
                return
            }
        }
    }
}

for k, v := range Pairs(counts) {
    fmt.Println(k, v)
}

选择规则很简单:只有一个值就用 Seq[V],天然有键值或索引值就用 Seq2[K, V]。后续 maps.Allmaps.Keysmaps.Valuesslices.All 这类 API 都围绕这套迭代器模型展开。

2. 泛型类型别名进入实验预览

类型别名原本不能带类型参数,泛型 API 在拆包和兼容导出名时会受限制。Go 1.23 通过实验开关预览泛型类型别名,让这类表达成为可能:

1
type Set[T comparable] = map[T]bool

它和 type Set[T comparable] map[T]bool 不一样。前者是别名,不创建新类型;后者是定义类型,会有自己的方法集和赋值规则。这个差异决定了它更适合 API 组织,而不是给类型增加新行为。

3. telemetry 让工具链有可观测数据

Go telemetry 用来统计 Go 命令和工具链的使用、性能和失败情况。默认只在本地记录,用户可以选择打开上传。它的目标不是观察业务程序,而是帮助 Go 团队理解工具链真实使用情况。

对应命令是 go telemetry。它可以查看当前模式,也可以切换为本地-only 或上传模式。这个机制关注的是 Go 命令本身,例如某个命令是否频繁失败、某个路径是否性能异常,而不是应用运行期数据。

4. uniquestructs 新增

unique 提供值规范化能力,同样的值可以得到可比较的 handle。

1
2
3
h1 := unique.Make("region-a")
h2 := unique.Make(strings.Clone("region-a"))
fmt.Println(h1 == h2) // true

它适合大量重复值的场景,例如标签、枚举字符串、路径片段。比较 handle 比比较完整值更便宜,也能避免重复存储同一份内容。

structs 当前很小,主要提供标记类型。它的价值更多在语言和工具链层面:给“结构体布局相关语义”一个标准位置。

语言与规范

range 表达式现在可以是特定签名的函数,例如 func(func(K, V) bool)。迭代器函数通过调用 yield 函数产生值,调用方可以继续使用熟悉的 for range 语法。

泛型类型别名通过 GOEXPERIMENT=aliastypeparams 预览,但跨包使用还有限制,真正稳定要看后续版本。

工具链与运行时

Go telemetry 用于收集工具链使用和故障统计。默认模式只把计数写在本地,不会上传;用户可以通过 go telemetry on 选择上传匿名统计。

go env -changedgo mod tidy -diffgo list -m -json 的字段增强都偏工程实用。go vet 新增 stdversion,能发现代码引用了高于当前 go.mod 版本的标准库符号。

go mod tidy -diff 尤其适合检查模块文件是否已经是 tidy 后状态。它不直接改文件,而是输出 diff 并以非零状态表示有差异,适合放进检查脚本。stdversion 则把“标准库 API 属于哪个 Go 版本”纳入静态检查,例如模块声明 go 1.22 却使用了 Go 1.23 才出现的 API,会被指出。

运行时的 timer 行为调整,time.Timertime.Ticker 更容易被 GC 回收,减少常见资源泄漏模式。

旧代码里经常为了避免 timer 泄漏写复杂的 stop/drain 模式:

1
2
3
4
if !timer.Stop() {
    <-timer.C
}
timer.Reset(d)

这类代码容易在并发场景里写错,尤其是 channel 是否已经有值、是否会阻塞、是否和其他 goroutine 同时读。Go 1.23 调整后,不再被引用的 timer/ticker 可以被 GC 回收,Timer 关联的 channel 也改成无缓冲语义,减少过期时间值残留带来的困惑。仍然持有并复用 timer 时,StopReset 的返回值语义仍要认真处理;变化的重点是“不再引用的 timer/ticker 不应长期拖住资源”。

标准库与新增包

新增公开包:

  • unique:提供值规范化和去重句柄。
  • structs:提供结构体标记用途的基础类型。

重要变化:

  • slices 增加更多迭代器相关能力。
  • crypto/tlsnet/httpruntime/tracesync/atomic 等继续增强。

小版本特殊变化

Go 1.23 系列共有 12 个小版本。函数迭代器、telemetry、unique 发布后,小版本里能看到 unique、runtime/trace、go/types 等新路径被继续修正。

这一系列的安全问题主要围绕解析和系统边界。go/parsergo/build/constraint 面向源码和构建约束解析,影响代码分析器、生成器和构建工具。encoding/gobdatabase/sqlnet/httpos/exec 都是典型边界包:反序列化、数据库连接、HTTP 请求、命令执行。os 的安全修复通常和文件、权限或平台行为有关。unique 出现在 Bug 修复里,说明新引入的规范化值机制也在小版本中继续稳定。

  • go1.23.1 修复 encoding/gobgo/build/constraintgo/parser 安全问题,同时修 compiler、go 命令、runtime、database/sqlgo/typesruntime/traceunique
  • go1.23.2 修 compiler、cgo、runtime、mapsos/exectimeunique
  • go1.23.5 修复 crypto/x509net/http 安全问题。
  • go1.23.7 修复 net/http 安全问题,并修 reflect、runtime、syscall。
  • go1.23.10 修复 net/httpos 安全问题。
  • go1.23.11 修复 go 命令安全问题,并修 compiler、linker、runtime。
  • go1.23.12 修复 database/sqlos/exec 安全问题。

参考

#Go #Golang #Go Release Notes #Go Iter #Go Unique