Go 1.18 Release Notes

Go 1.18 简介

最新的 Go 版本 1.18 是一个重要的版本,包含了对语言、工具链实现、运行时和库的更改。Go 1.18 在Go 1.17发布七个月后推出。与往常一样,该版本保持了 Go 1 的兼容性承诺。我们预计几乎所有 Go 程序都能像以前一样继续编译和运行。

语言变更

泛型

Go 1.18 实现了类型参数提案中的泛型特性。该变更完全向后兼容。

泛型代码在生产环境中尚未经过充分测试。建议在生产部署时保持谨慎。虽然我们不计划进行破坏性更改,但在极少数情况下,未来版本可能需要解决规范中的不一致问题。

语法变更

详情请参见语言规范

  • 函数类型声明现在接受类型参数
  • 参数化函数和类型可以通过在其后紧跟方括号中的类型参数列表来实例化。
  • 新标记 ~ 已添加到运算符和标点符号集合中。
  • 接口类型的语法现在允许嵌入任意类型(不仅是接口的类型名称)以及联合类型和~T 类型元素。此类接口只能用作类型约束。接口现在既定义了一组类型,也定义了一组方法。
  • 新的预声明标识符 any 是空接口的别名。它可以用来替代 interface{}。
  • 新的预声明标识符 comparable 是一个接口,它表示所有可以使用==或!=进行比较的类型集合。它只能用作(或嵌入在)类型约束中。
// 1. 泛型函数
// [T any] 是类型参数列表,T 是类型参数,any 是约束
func PrintSlice[T any](s []T) {
    for _, v := range s {
        println(v)
    }
}
 
// 2. 泛型类型
// Vector 是一个切片,但它存储的元素类型由 T 决定
type Vector[T any] []T
func main() {
    // 实例化泛型函数:显式指定 [int]
    // 此时 PrintSlice 内部的 T 变成了 int
    PrintSlice[int]([]int{1, 2, 3})
 
    // 实例化泛型类型:显式指定 [string]
    // v 现在的类型是 []string
    var v Vector[string] = []string{"hello", "world"}
}

波浪号 ~ 用于表示“底层类型”。比如 type MyInt int,虽然 MyInt 和 int 是不同类型,但 MyInt 的底层类型是 int。~int 会同时匹配 int 和 MyInt。

type MyInt int
 
// 定义一个约束,包含 ~int
// 这意味着:只要底层类型是 int 的类型都可以
type Integer interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}
 
// 这个函数既可以接收 int,也可以接收 MyInt
func Add[T Integer](a, b T) T {
    return a + b
}
 
func main() {
    var a int = 1
    var b MyInt = 2
    
    Add(a, a) // 合法
    Add(b, b) // 合法,因为 MyInt 的底层类型是 int,匹配 ~int
}
// 定义一个接口作为类型约束
// 它不包含方法,而是包含一组允许的类型
type Number interface {
    int | float64 | float32 // 联合类型:是 int 或 float64 或 float32
}
 
// 使用该接口作为约束
func Max[T Number](x, y T) T {
    if x > y {
        return x
    }
    return y
}
// 以前的写法
func OldPrint(v interface{}) {
    // ...
}
 
// 现在的写法 (完全等价)
func NewPrint(v any) {
    // ...
}
 
// 在泛型中常用作约束,表示不限制类型
func Identity[T any](v T) T {
    return v
}
// 错误示例:如果不加 comparable,编译器会报错,因为并非所有类型都能用 == 比较(如 slice, map)
// func Index[T any](s []T, x T) int { ... if v == x ... } // 编译错误
 
// 正确示例
func Index[T comparable](s []T, x T) int {
    for i, v := range s {
        // 这里可以使用 ==,因为 T 被约束为 comparable
        if v == x {
            return i
        }
    }
    return -1
}
 
func main() {
    Index([]int{1, 2, 3}, 2)       // 合法,int 是 comparable
    // Index([][]int{{1}}, []int{1}) // 非法!slice 不是 comparable
}

实验性包 (x/exp)

以下包位于 x/exp 中,API 不受 Go 1 保证:

import "golang.org/x/exp/constraints"
 
// Min 使用 constraints.Ordered 约束,
// 使得函数可以接受任何支持 < 操作符的类型(如 int, float, string 等)
func Min[T constraints.Ordered](x, y T) T {
    if x < y {
        return x
    }
    return y
}
import (
    "fmt"
    "golang.org/x/exp/slices"
)
 
func main() {
    data := []int{5, 2, 9, 1}
    
    // 泛型排序
    slices.Sort(data)
    fmt.Println(data) // Output: [1 2 5 9]
 
    // 泛型查找
    // Contains 返回 bool,Index 返回下标
    if slices.Contains(data, 9) {
        idx := slices.Index(data, 9)
        fmt.Println("Found at index:", idx)
    }
}
import (
    "fmt"
    "golang.org/x/exp/maps"
)
 
func main() {
    scores := map[string]int{"Alice": 95, "Bob": 88}
 
    // 获取所有键 (Keys) 和所有值 (Values)
    keys := maps.Keys(scores)
    values := maps.Values(scores)
    
    fmt.Println(keys)   // Output: [Alice Bob] (顺序不固定)
    fmt.Println(values) // Output: [95 88]
 
    // 浅拷贝 map
    copyMap := maps.Clone(scores)
}

已知限制

当前的泛型实现存在以下已知限制:

  • Go 编译器无法处理泛型函数或方法内部的类型声明。我们希望在未来的版本中支持这一功能。
  • Go 编译器不接受将类型参数类型的参数用于预声明函数 real、imag 和 complex。我们希望在未来的版本中取消这一限制。
  • Go 编译器仅支持在类型参数类型 P 的值 x 上调用方法 m,前提是 m 由 P 的约束接口显式声明。同样,方法值 x.m 和方法表达式 P.m 也仅在 m 由 P 显式声明时才受支持,即便由于 P 中的所有类型都实现了 m,m 可能位于 P 的方法集中也是如此。我们希望在未来的版本中移除这一限制。
  • Go 编译器不支持访问结构体字段 x.f,其中 x 是类型参数类型,即使该类型参数的类型集中的所有类型都有字段 f。我们可能会在未来的版本中移除这一限制。
  • 不允许在结构体类型中嵌入类型参数或指向类型参数的指针作为未命名字段。同样,也不允许在接口类型中嵌入类型参数。目前尚不清楚将来是否会允许这些做法。
  • 一个包含多个项的联合元素不能包含具有非空方法集的接口类型。目前尚不清楚将来是否会允许这种情况。

泛型对 Go 生态系统来说也是一项重大变革。虽然我们已经更新了几个核心工具,使其支持泛型,但还有很多工作要做。剩下的工具、文档和库要跟上这些语言变化,还需要时间。

错误修复

  • 未使用变量:修复了函数字面量中未报告“已声明但未使用”变量的问题(issue #8560)。
  • 常量溢出:修复了将溢出的符文常量(如 '1' << 32)传递给 print/println 时未报错的问题。

go vet 此前已能检测上述问题,受影响代码量极少。

工具

Fuzzing (模糊测试)

Go 1.18 引入了原生的模糊测试支持。详情参见 Fuzzing Landing Page

模糊测试会消耗大量内存,且生成的测试覆盖数据存储在 $GOCACHE/fuzz 中,可能会占用大量磁盘空间。

Go 命令

  • go get:在模块模式下不再构建或安装包,仅用于调整 go.mod 依赖(-d 标志默认启用)。安装可执行文件请使用 go install example.com/cmd@latest
  • go work:新增工作区模式。通过 go.work 文件或 GOWORK 环境变量,允许跨多个模块进行开发。参见 Go Workspaces
  • 版本控制信息go 命令现在会将 VCS 信息(commit、时间、dirty)和构建标志嵌入二进制文件中。可通过 go version -m filedebug/buildinfo 查看。禁用:-buildvcs=false
  • go modgraph, vendor, verify, why 不再自动更新 go.mod/go.sum
  • go mod download:对于 Go 1.17+ 模块,默认仅下载显式依赖。使用 go mod download all 下载所有依赖。
  • go build -asan:支持 Address Sanitizer(-fsanitize=address)。
  • //go:build:全面支持该语法,go fix 将移除旧的 // +build

其他工具

  • Gofmt:支持并发格式化,速度显著提升。
  • Vet:支持泛型代码检查;增强了 copylock, printf, sortslice 等检查精度。