Skip to content
Go
泛型详解 (Go 1.18+)

泛型详解 (Go 1.18+)

Go 1.18 引入了泛型(类型参数),这是 Go 语言历史上最大的语言变更。泛型让你可以编写类型安全可复用的代码,而不需要牺牲类型安全去使用 interface{}


1. 为什么需要泛型?

在没有泛型的时代,你有两种选择:

// 方案 A:为每种类型写一份代码(类型安全,但重复)
func MaxInt(a, b int) int { if a > b { return a }; return b }
func MaxFloat(a, b float64) float64 { if a > b { return a }; return b }

// 方案 B:用 interface{}(通用,但失去类型安全 + 反射开销)
func MaxAny(a, b interface{}) interface{} { /* 需要大量的类型断言 */ }

泛型给了你第三种选择:写一次代码,适用于多种类型,保持类型安全


2. 基础语法

2.1 泛型函数

// 语法:func 函数名[类型参数 约束](参数列表) 返回值
func Max[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// 调用
x := Max[int](3, 5)          // 显式指定类型参数
y := Max(3.14, 2.71)         // 类型推断:自动推断为 float64
z := Max("hello", "world")   // 类型推断:自动推断为 string

💡 何时可以省略类型参数:当编译器能从函数参数中推断出类型时,可以省略 [T]。如果只有返回值涉及类型参数(无参函数),则必须显式指定。

2.2 泛型类型

// 泛型结构体
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T  // 零值变量
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

// 使用
intStack := Stack[int]{}
intStack.Push(42)
v, _ := intStack.Pop() // v 是 int,值为 42

strStack := Stack[string]{}
strStack.Push("hello")

2.3 类型参数命名规范

// 约定:
// T  — 单一类型参数(最常用)
// T, U, V — 多个类型参数
// K, V — 键值对(如 map 的 key 和 value)
// E  — 元素类型(如 slice 的元素)
// S  — slice 类型

// 示例
func Keys[K comparable, V any](m map[K]V) []K { ... }
func Contains[E comparable](s []E, v E) bool { ... }

3. 类型约束 (Type Constraints)

约束定义了类型参数必须满足的条件。它是泛型的核心。

3.1 any — 无约束

// any 是 interface{} 的别名(Go 1.18 引入)
// 表示接受任何类型
func Print[T any](v T) {
    fmt.Println(v)
}

3.2 comparable — 可比较类型

// comparable 是 Go 预声明的约束,表示可以用 == 和 != 比较的类型
// 不包括 slice、map、function(它们不能直接用 == 比较)

func Equal[T comparable](a, b T) bool {
    return a == b
}

Equal(1, 2)        // ✅
Equal("hi", "hi")  // ✅
// Equal([]int{}, []int{}) // ❌ 编译错误:slice 不满足 comparable

🚨 陷阱comparable 只保证 ==!= 可用。如果想用 ><,需要使用 cmp.Ordered 约束。

3.3 自定义约束 — 接口即约束

在 Go 泛型中,接口天然就是类型约束

// 任何实现了 fmt.Stringer 的类型都可以作为 T
func Stringify[T fmt.Stringer](v T) string {
    return v.String()
}

// 使用
type User struct { Name string }
func (u User) String() string { return u.Name }

s := Stringify(User{"Alice"}) // ✅

3.4 联合类型约束 (Union)

// 用 | 列举允许的具体类型
type Number interface {
    int | int32 | int64 | float32 | float64
}

func Sum[T Number](a, b T) T {
    return a + b
}

Sum(1, 2)       // ✅ int
Sum(1.5, 2.3)   // ✅ float64
// Sum("a", "b") // ❌ string 不在 Number 中

3.5 ~ — 近似约束 (Approximation)

// ~T 表示底层类型为 T 的所有类型(包括自定义类型)

type MyInt int        // 底层类型是 int
type YourInt int      // 底层类型也是 int

// 不使用 ~:只接受 int,不接受 MyInt
type StrictInt interface {
    int
}

// 使用 ~:接受 int 以及所有底层类型为 int 的自定义类型
type FlexibleInt interface {
    ~int | ~int64 | ~float64
}

func Double[T FlexibleInt](v T) T {
    return v * 2
}

Double(MyInt(5))   // ✅ MyInt 的底层类型是 int
Double(YourInt(3)) // ✅
Double(3.14)       // ✅ float64

// 对比:
// Double[int](5)  — int 满足 ~int ✅
// Double[MyInt](5) — MyInt 底层类型是 int,满足 ~int ✅
// Double[string]("x") — ❌ 底层类型不是 int

💡 何时用 ~:如果你希望泛型函数能接受用户自定义的类型别名,使用 ~。标准库的 constraints.Ordered 使用了 ~(实际用 cmp.Ordered)。

3.6 约束字面量(内联约束)

// 约束可以直接写在函数签名中,不需要提前声明接口
func Add[T int | float64 | string](a, b T) T {
    return a + b // int/float64/string 都支持 +
}

// 也可以混用 ~
func Scale[T ~int | ~float64](v T, factor float64) T {
    return T(float64(v) * factor)
}

🚨 注意:内联约束不能包含方法。如果需要方法约束,必须定义接口类型。

3.7 约束的嵌套与组合

// 接口可以嵌入其他接口作为约束
type ComparableHasher[T comparable] interface {
    comparable
    Hash() uint64
}

// 联合 + 方法不能混在同一个接口中
// ❌ type Bad interface { int | string; String() string }  // 不允许!

// ✅ 正确做法:分开定义
type NumericStringer interface {
    ~int | ~float64       // 只有类型联合
}

4. 类型推断

Go 编译器可以从函数参数中推断类型参数,但有一定限制。

4.1 函数参数推断

func First[T any](s []T) T {
    return s[0]
}

names := []string{"Alice", "Bob"}
first := First(names) // 推断 T = string,不需要写成 First[string](names)

4.2 无法推断的场景

// 场景 1:类型参数只出现在返回值中
func New[T any]() *T {
    var v T
    return &v
}
// p := New()        // ❌ 无法推断 T
p := New[int]()      // ✅ 必须显式指定

// 场景 2:类型参数只出现在多个参数的部分类型中
func Convert[From, To any](from From) To {
    // 无法推断 To
}
// t := Convert[int](5) // ❌ 只指定了 From=5,To 仍无法推断
t := Convert[int, string](5) // ✅ 两个都要指定

4.3 Go 1.21+ 的类型推断增强

// Go 1.21 增强了类型推断,更多场景可以省略类型参数
// 例如:当约束中的类型可以被函数的非类型参数推断时

// Go 1.21 之前可能需要对某些复杂推断显式指定
// Go 1.21 之后大部分场景都可以自动推断

5. 泛型与方法

5.1 泛型类型的方法

type Set[T comparable] struct {
    data map[T]struct{}
}

func NewSet[T comparable]() *Set[T] {
    return &Set[T]{data: make(map[T]struct{})}
}

// 方法可以使用类型的类型参数 T
func (s *Set[T]) Add(v T) {
    s.data[v] = struct{}{}
}

func (s *Set[T]) Contains(v T) bool {
    _, ok := s.data[v]
    return ok
}

// ✅ 方法只使用类型的类型参数 T(不引入新类型参数)——合法
func (s *Set[T]) Filter(fn func(T) bool) []T {
    var result []T
    for k := range s.data {
        if fn(k) {
            result = append(result, k)
        }
    }
    return result
}

// ✅ 同样只使用 T,不引入新类型参数
func (s *Set[T]) ToSlice() []T {
    result := make([]T, 0, len(s.data))
    for k := range s.data {
        result = append(result, k)
    }
    return result
}

5.2 🚨 关键限制:方法不能引入自己的类型参数

这是 Go 泛型最大的限制之一。泛型类型的方法可以使用类型的类型参数(T),但不能引入方法自己的类型参数

// 🔬 核心区分:"使用已有的" vs "引入新的"

// ✅ 合法:方法只使用类型本身已有的 T
func (s *Set[T]) Filter(fn func(T) bool) []T { ... }
//                    ↑ 只有 T(来自 Set[T]),没有引入新类型参数

// ❌ 非法:方法引入了自己的类型参数 U(Go 1.18 ~ 1.26 的限制)
// func (s *Set[T]) Map[U any](fn func(T) U) []U { ... }
//                    ^^^^^^ U 是方法"自己"的类型参数 → 编译错误!

// ❌ 同样非法:方法引入 U
// func (s *Set[T]) FilterTo[U any](fn func(T) (U, bool)) []U { ... }
//                         ^^^^^^ U 是方法自己的 → 编译错误!

Filter 为什么可以,Map 为什么不行?

关键区别不在于"功能",而在于类型参数的归属

方法 使用的类型参数 是否合法 原因
Add(v T) T(类型的) T 来自 Set[T],不是方法自己的
Filter(fn func(T) bool) []T T(类型的) 输入 T、输出 T,始终只用已有类型
Map[U any](fn func(T) U) []U U(方法自己的 U 是方法额外引入的,编译器拒绝
FilterTo[U any](fn func(T) (U, bool)) []U U(方法自己的 同上

本质上:只要方法的返回值类型参数全部来自类型本身(如 []T(T, error)Set[T]),就合法。一旦需要"从 T 映射到另一个任意类型 U",就必然引入方法自己的类型参数,在 Go 1.26 之前不合法。

// ✅ 替代方案 1:改为包级函数(最常用)
func MapSet[T comparable, U any](s *Set[T], fn func(T) U) []U {
    var result []U
    for k := range s.data {
        result = append(result, fn(k))
    }
    return result
}

// ✅ 替代方案 2:把 U 提升到结构体的类型参数中
type Set2[T comparable, U any] struct {
    data map[T]struct{}
    fn   func(T) U
}
// 代价:类型的"形状"变了,实例化时必须同时指定 U

🚨 实际影响:你不能为 *http.Request 写一个 JSONBody[T any]() (T, error) 方法。这是 Go 泛型最常被抱怨的限制之一。

🔬 Go 1.27 展望——泛型方法即将到来

Go 创始人 Robert Griesemer 已于 2026 年 1 月提交了泛型方法提案(#77273),该提案已被 Proposal-Accepted 且实现完成(目前在 GOEXPERIMENT 之后)。预计 Go 1.27(约 2026 年 8 月)将正式支持泛型方法

核心设计思路:泛型方法不实现接口中的方法(接口方法语法上不允许有类型参数),但可以作为具体类型的普通方法使用。这解决了链式调用、Builder 模式等痛点:

// 预计 Go 1.27 的写法:
func (p *Pipeline[T]) Map[U any](fn func(T) U) *Pipeline[U] {
    out := make([]U, len(p.items))
    for i, v := range p.items { out[i] = fn(v) }
    return &Pipeline[U]{items: out}
}

// 链式调用变得自然:
r := NewPipeline([]int{1,2,3}).
     Map(square).
     Filter(isEven)

6. 泛型与接口

6.1 接口值 vs 类型参数

// 接口方式:运行时多态,有装箱开销
func StringifyInterface(v fmt.Stringer) string {
    return v.String()  // 通过接口方法表动态分发
}

// 泛型方式:编译期单态化,无装箱开销
func StringifyGeneric[T fmt.Stringer](v T) string {
    return v.String()  // 编译时确定具体类型,直接调用
}
特性 接口 泛型
多态方式 运行时(动态分发) 编译时(单态化)
性能 接口装箱 + 间接调用 零开销
二进制大小 一份代码 每种类型一份(代码膨胀)
灵活性 可存不同类型的切片 切片必须同类型
适用场景 需要异构集合 需要类型安全 + 性能

6.2 何时用泛型,何时用接口?

// ✅ 用泛型:需要"同类型"的操作
func Clone[T any](s []T) []T {
    result := make([]T, len(s))
    copy(result, s)
    return result
}

// ✅ 用接口:需要"不同类型"的集合
type Animal interface { Speak() string }
animals := []Animal{Dog{}, Cat{}, Bird{}} // 不同类型存入同一个 slice

💡 经验法则:如果你发现自己在写"容器"或"算法"(对同类型元素的集合操作),用泛型。如果你需要存"不同具体类型的值",用接口。


7. 标准库中的泛型

Go 1.21 开始,标准库中出现了大量泛型工具包。

7.1 slices 包 (Go 1.21+)

import "slices"

// 搜索与判断
slices.Contains([]int{1, 2, 3}, 2)          // true
slices.Index([]string{"a", "b"}, "b")       // 1
slices.Equal([]int{1, 2}, []int{1, 2})      // true

// 排序
nums := []int{3, 1, 4, 1, 5}
slices.Sort(nums)                            // [1, 1, 3, 4, 5]
slices.SortFunc(users, func(a, b User) int {
    return cmp.Compare(a.Age, b.Age)
})

// 去重与压缩
slices.Compact([]int{1, 1, 2, 3, 3})       // [1, 2, 3]
slices.Clip(nums)                            // 释放未使用的容量

// 插入与删除
nums = slices.Insert(nums, 2, 99)           // 在索引 2 插入 99
nums = slices.Delete(nums, 1, 3)            // 删除 [1, 3)

// Go 1.22+
slices.Concat([]int{1, 2}, []int{3, 4})     // [1, 2, 3, 4]
slices.Repeat([]int{1, 2}, 3)               // [1, 2, 1, 2, 1, 2]

slices 包完全替代了过去需要手写的 slice 工具函数。所有操作都是类型安全的。

7.2 maps 包 (Go 1.21+)

import "maps"

m := map[string]int{"a": 1, "b": 2}

// 克隆
m2 := maps.Clone(m)        // 浅拷贝

// 拷贝
maps.Copy(m2, map[string]int{"c": 3})

// 删除符合条件的键值对
maps.DeleteFunc(m, func(k string, v int) bool {
    return v < 2  // 删除值小于 2 的
})

// 比较两个 map
maps.Equal(m, m2)          // true/false

// 获取所有键 / 值
keys := slices.Sorted(maps.Keys(m))   // ["a", "b"]
values := slices.Sorted(maps.Values(m)) // [1, 2]

7.3 cmp 包 (Go 1.21+)

import "cmp"

// 比较两个有序值
cmp.Compare(1, 2)     // -1
cmp.Compare(2, 1)     // 1
cmp.Compare(2, 2)     // 0
cmp.Compare("a", "b") // -1

// Go 1.22+: 返回第一个非零值
host := cmp.Or(cfg.Host, "localhost")       // 如果 cfg.Host 为 "",用默认值
timeout := cmp.Or(0, 30)                    // 如果第一个为零值,用 30

💡 cmp.Ordered 约束:包含所有支持 <<=>>= 比较的类型(整数、浮点数、字符串)。

7.4 sync/atomic 泛型类型 (Go 1.19+)

import "sync/atomic"

var counter atomic.Int64
counter.Add(1)
val := counter.Load()

var ready atomic.Bool
ready.Store(true)

var ptr atomic.Pointer[MyStruct]
ptr.Store(&MyStruct{Name: "test"})

7.5 sync 泛型函数 (Go 1.21+)

// OnceFunc: 将一个函数包装为只执行一次的函数
var initDB = sync.OnceFunc(func() {
    // 初始化数据库连接(只执行一次)
    db = connectDB()
})

initDB() // 第一次调用执行
initDB() // 后续调用无操作

// OnceValue: 返回只计算一次的值
var getConfig = sync.OnceValue(func() *Config {
    return loadConfig() // 只加载一次,缓存结果
})

// OnceValues: 返回只计算一次的 (T, error)
var getData = sync.OnceValues(func() ([]Item, error) {
    return fetchItems() // 只请求一次
})

7.6 iter 包 — 泛型迭代器 (Go 1.23+)

Go 1.23 引入了语言级特性 range-over-func 和配套的 iter 包。这是泛型在 Go 中最重要的应用之一:通过将迭代逻辑抽象为可组合的泛型函数,让自定义容器也能被 for range 遍历。

核心类型:SeqSeq2

package iter

// Seq[V] — 单值迭代器(如 slice 的值、set 的元素)
type Seq[V any] func(yield func(V) bool)

// Seq2[K, V] — 键值对迭代器(如 map 的 key-value、slice 的 index-value)
type Seq2[K, V any] func(yield func(K, V) bool)

整个 iter 包只有 4 个核心类型:SeqSeq2PullPull2。它们共同组成了 Go 的"可组合迭代器"基础设施。

🔬 yield 是什么?

yield 是迭代器的回调函数。它做了两件事:

  1. 产出值:将当前元素传递给 for range 循环体。
  2. 返回 bool:告诉迭代器"是否继续"——返回 false 表示消费者想提前退出(breakreturn)。
// 这是一个 Seq[int] 类型,它就是一个函数:
//   func(yield func(int) bool)
//
// 当你 for range 它时,Go 编译器自动:
//   1. 创建一个 yield 回调
//   2. 调用你的 Seq 函数,传入 yield
//   3. 你的函数每次调用 yield(val) 就是在产出值
//   4. yield 返回 false 时(用户 break 了),你应立即停止

countdown := func(yield func(int) bool) {
    for i := 3; i > 0; i-- {
        if !yield(i) {  // ← yield 返回 false 表示消费者 break 了
            return       // 停止迭代
        }
    }
}

for v := range countdown {
    fmt.Println(v)  // 3, 2, 1
    if v == 2 {
        break  // yield 返回 false,countdown 退出循环
    }
}

🔬 执行流程for v := range seq 时,编译器将循环体包装成 yield 函数传给 seq。每次 yield(v) 调用,控制权转移到循环体;循环体执行完或 break 后,控制权回到 yield 的返回点。

iter.Pull / iter.Pull2 — 从推变拉

默认的 Seq推模式(push):迭代器主动推数据给 yielditer.Pull 将其转为拉模式(pull):

// Pull 返回 (next, stop) 两个函数
func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func())

// next() 返回下一个值 + 是否还有更多
// stop() 提前终止迭代(释放资源)

seq := slices.Values([]int{10, 20, 30})
next, stop := iter.Pull(seq)
defer stop()  // 重要:释放内部 goroutine

for {
    v, ok := next()
    if !ok { break }
    fmt.Println(v)  // 10, 20, 30
}

性能提示iter.Pull 内部使用 goroutine + channel 实现协程切换,比直接 for range 慢约 100-200 倍(~570k ns/op vs ~3k ns/op)。除非你需要手动控制步进(如交错遍历两个迭代器),否则应始终用 for range

Push vs Pull 对比

模式 实现 优点 缺点
Push (Seq) for range 简洁、高效、无 goroutine 开销 难以交错遍历或暂停/恢复
Pull (Pull) next()/stop() 灵活控制步进 有 goroutine 开销、必须调用 stop()

标准库中的 Iterator 生产者

// === slices 包 ===
slices.All(s)         // Seq2[int, E] — (索引, 值)
slices.Values(s)      // Seq[E] — 仅值
slices.Backward(s)    // Seq2[int, E] — 反向迭代 (索引, 值)
slices.Chunk(s, n)    // Seq[[]E] — 按块分组,每组 n 个元素

// === maps 包 ===
maps.All(m)           // Seq2[K, V] — (键, 值)
maps.Keys(m)          // Seq[K] — 仅键
maps.Values(m)        // Seq[V] — 仅值

// === strings 包 (Go 1.24+) ===
strings.Lines(s)            // Seq[string] — 按行分割(保留换行符)
strings.SplitSeq(s, sep)    // Seq[string] — 按分隔符分割
strings.SplitAfterSeq(s, sep) // Seq[string] — 分割后保留分隔符
strings.FieldsSeq(s)        // Seq[string] — 按空白字符分割
strings.FieldsFuncSeq(s, f) // Seq[string] — 按自定义谓词分割

// === bytes 包 (Go 1.24+) ===
bytes.Lines(b)              // Seq[[]byte]
bytes.SplitSeq(b, sep)      // Seq[[]byte]
// ... 与 strings 对应

标准库中的 Iterator 消费者

// 从迭代器收集到具体类型
slices.Collect(seq)   // Seq[E] → []E
slices.Sorted(seq)    // Seq[E] → []E(排序后)
maps.Collect(seq)     // Seq2[K, V] → map[K]V
maps.Insert(m, seq)   // 将 Seq2 插入已有 map

编写自定义迭代器

// 模式 1:闭包迭代器(适合简单场景)
func Fibonacci(n int) iter.Seq[int] {
    return func(yield func(int) bool) {
        a, b := 0, 1
        for i := 0; i < n; i++ {
            if !yield(a) { return }
            a, b = b, a+b
        }
    }
}

// 使用
for v := range Fibonacci(10) {
    fmt.Println(v)  // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
}

// 模式 2:类型方法返回迭代器(为自定义容器提供 for range 支持)
type Set[T comparable] struct { data map[T]struct{} }

func (s *Set[T]) All() iter.Seq[T] {
    return func(yield func(T) bool) {
        for k := range s.data {
            if !yield(k) { return }
        }
    }
}

// 使用自定义容器的迭代器
set := NewSet(1, 2, 3)
for v := range set.All() {
    fmt.Println(v)
}

🔬 带资源管理的迭代器

// 迭代器中可以使用 defer 确保资源释放
func ReadLines(filename string) iter.Seq[string] {
    return func(yield func(string) bool) {
        f, err := os.Open(filename)
        if err != nil { return }
        defer f.Close()  // ← 即使消费者 break,defer 也会执行!

        scanner := bufio.NewScanner(f)
        for scanner.Scan() {
            if !yield(scanner.Text()) {
                return  // break → defer f.Close() 执行
            }
        }
    }
}

// 消费者可以安全 break,文件句柄不会泄漏
for line := range ReadLines("/var/log/app.log") {
    if strings.Contains(line, "ERROR") {
        break  // 文件会自动关闭
    }
}

💡 关键保证:迭代器函数体中的 defer 在消费者 break也会执行。Go runtime 保证:当 yield 返回 false,迭代器函数正常返回,所有 defer 正确执行。这意味着资源清理是安全的。

可组合的迭代器适配器

// 编写自己的适配器:接收 Seq,返回新的 Seq
func Filter[V any](seq iter.Seq[V], fn func(V) bool) iter.Seq[V] {
    return func(yield func(V) bool) {
        for v := range seq {
            if fn(v) {
                if !yield(v) { return }
            }
        }
    }
}

func Map[T, U any](seq iter.Seq[T], fn func(T) U) iter.Seq[U] {
    return func(yield func(U) bool) {
        for v := range seq {
            if !yield(fn(v)) { return }
        }
    }
}

func Take[V any](seq iter.Seq[V], n int) iter.Seq[V] {
    return func(yield func(V) bool) {
        count := 0
        for v := range seq {
            if count >= n { return }
            if !yield(v) { return }
            count++
        }
    }
}

// 链式组合 — 像 Unix 管道
nums := slices.Values([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
odd := Filter(nums, func(n int) bool { return n%2 != 0 })
doubled := Map(odd, func(n int) int { return n * 2 })
first3 := Take(doubled, 3)
// first3 产出:2, 6, 10
// 注意:迭代器是惰性的,直到 for range 时才执行

常见陷阱

陷阱 说明
迭代器被多次消费 多数迭代器是单次使用。重复 for range 第二次得到空结果
Pull 不调 stop() goroutine 泄漏!始终 defer stop()
yield 返回后继续迭代 yield 返回 false 后必须立即 return,否则继续迭代导致不可预期行为
yield 执行期间持有锁 yield 可能执行任意逻辑(包括获取同一个锁),容易死锁

8. 泛型类型别名 (Go 1.24)

Go 1.24 支持了泛型类型别名:类型别名(=)现在可以像类型定义一样声明类型参数。

为什么需要?

当你想把泛型类型从一个包迁移到另一个包时,泛型类型别名让旧包的引用可以无缝指向新包,且保持类型同一性

// 旧包 pkg1 定义了一个泛型类型
package pkg1
type Set[K comparable] map[K]struct{}

// 新包 pkg2 使用泛型类型别名提供向后兼容
package pkg2
import "xxx/pkg1"

// ✅ Go 1.24+:泛型类型别名
type Set[K comparable] = pkg1.Set[K]
// 使用 = 而非普通的 type definition,保持类型同一性
// pkg2.Set[int] 和 pkg1.Set[int] 是完全相同的类型!

核心特性

// 1. 可以部分实例化类型参数
type StringSet = Set[string]  // 固定为 string 版本

// 2. 可以"瘦身"约束(别名的约束只需满足原类型的约束)
type OrderedSet[K cmp.Ordered] = Set[K]  // 比 comparable 更窄的约束

// 3. 泛型类型字面量也可以用作别名
type Point3D[E any] = struct{ x, y, z E }

p := Point3D[float64]{1.0, 2.0, 3.0}

🚨 注意:泛型类型别名是 Go 1.23 的实验特性(GOEXPERIMENT=aliastypeparams),Go 1.24 起默认启用。


9. 递归泛型约束 (Go 1.26)

Go 1.26 解除了泛型中"类型不能自引用"的限制,支持了 F-bounded 多态:接口约束中可以引用自身。

🔬 什么是递归泛型?

以前 Go 禁止泛型类型在自己的类型参数列表里引用自己(循环自引用)。Go 1.26 放开了这个限制。

// Go 1.26 之前:编译错误
// type Adder[A Adder[A]] interface { ... }  ← 循环引用,不让用

// Go 1.26:合法!
type Adder[A Adder[A]] interface {
    Add(A) A
}

// 使用递归约束
func SumAll[A Adder[A]](values []A) A {
    if len(values) == 0 {
        var zero A
        return zero
    }
    result := values[0]
    for _, v := range values[1:] {
        result = result.Add(v)  // 编译时保证类型安全
    }
    return result
}

实际应用场景

// 场景 1:可合并类型(CRDT / 分布式数据结构)
type Mergeable[T Mergeable[T]] interface {
    Merge(other T) T
}

type Counter struct { value int }
func (c Counter) Merge(other Counter) Counter {
    return Counter{c.value + other.value}
}

func MergeAll[T Mergeable[T]](items []T) T {
    result := items[0]
    for _, item := range items[1:] {
        result = result.Merge(item)
    }
    return result
}

// 场景 2:树节点(确保同类型操作)
type TreeNode[T TreeNode[T]] interface {
    Children() []T
}

func WalkTree[T TreeNode[T]](root T, fn func(T)) {
    fn(root)
    for _, child := range root.Children() {
        WalkTree(child, fn)
    }
}

// 场景 3:链式 Builder(每个步骤返回自身类型)
type Builder[T Builder[T]] interface {
    WithName(name string) T
    WithAge(age int) T
}

💡 为什么之前禁止? 编译器需要确定泛型类型的"形状"(shape)以进行单态化。递归约束让类型推导变得复杂。Go 1.26 的编译器改进使这成为可能。


10. 🔬 深入原理:单态化 (Monomorphization)

Go 使用单态化(而非类型擦除)实现泛型:

源码:     Max[T](a, b T) T
             ↓ 编译时
生成:     Max[int](a, b int) int
          Max[float64](a, b float64) float64
          Max[string](a, b string) string

影响

方面 效果
运行时性能 ✅ 零开销(没有装箱、没有虚函数调用)
编译速度 ⚠️ 每种实例化都要编译一次
二进制体积 ⚠️ 每种实例化产生一份代码副本
// 相同的底层类型会共享同一份代码
type MyInt int
Max(MyInt(3), MyInt(5)) // 底层类型是 int,与 Max[int] 共享实现
Max(int(3), int(5))      // 与上面共享同一份 GC shape

🔬 GC Shape:Go 的泛型实现基于 GC shape(而非完全独立的单态化)。具有相同底层类型和相同 GC 行为的类型共享同一份编译代码,减少了代码膨胀。


11. 🚨 泛型常见陷阱

陷阱 1:不能对类型参数使用类型断言

func Process[T any](v T) {
    // ❌ 不能对类型参数 T 做类型断言
    // s := v.(string)  // 编译错误!

    // ✅ 正确做法:先转为 interface{},再断言
    if s, ok := any(v).(string); ok {
        fmt.Println("it's a string:", s)
    }
}

陷阱 2:不能在类型参数上使用 switch

func Describe[T any](v T) {
    // ❌ 不能直接 switch v.(type)
    // switch v.(type) { ... } // 编译错误!

    // ✅ 正确做法:转为 any
    switch val := any(v).(type) {
    case string:
        fmt.Println("string:", val)
    case int:
        fmt.Println("int:", val)
    default:
        fmt.Printf("other: %T\n", val)
    }
}

陷阱 3:泛型类型的方法不能有额外的类型参数

type Container[T any] struct { val T }

// ❌ 方法不能引入新的类型参数
// func (c Container[T]) Map[U any](fn func(T) U) Container[U] { ... }

// ✅ 改写成包级函数
func MapContainer[T, U any](c Container[T], fn func(T) U) Container[U] {
    return Container[U]{val: fn(c.val)}
}

陷阱 4:comparable 不能用于 map key 的泛型约束

// ✅ comparable 可以用作 map key 的约束
func Keys[K comparable, V any](m map[K]V) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

// ❌ any 约束不能用作 map key
// func Bad[K any, V any](m map[K]V) []K { ... } // K must be comparable

陷阱 5:约束中的方法不能有类型参数变化

// ❌ 约束接口中的方法不能有泛型参数
// type Container[T any] interface {
//     Map[U any](func(T) U) Container[U]  // 不允许!
// }

陷阱 6:nil 不能直接赋值给泛型变量

func New[T any]() *T {
    // ❌ 不能 return nil(编译器不知道 T 的具体类型)
    // return nil

    // ✅ 正确做法
    var zero T
    return &zero
}

陷阱 7:类型参数的零值比较

func IsZero[T comparable](v T) bool {
    var zero T
    return v == zero  // ✅ 对于 comparable 类型可以这样比较
}

// 但如果 T 只是 any,就不能比较零值
func IsZeroAny[T any](v T) bool {
    var zero T
    // return any(v) == any(zero) // 如果 T 是 slice,会 panic!
    return false
}

陷阱 8:泛型与 JSON 序列化

type Response[T any] struct {
    Code int `json:"code"`
    Data T   `json:"data"`
}

// ✅ 只要 T 的具体类型可以被 json 序列化,Response[T] 就可以
resp := Response[User]{Code: 200, Data: User{Name: "Alice"}}
json.Marshal(resp) // ✅

// 🚨 注意:omitempty + 泛型
// 如果 T 是 struct,omitempty 对 struct 无效(见 encoding-json.md 陷阱 4)
type Wrapper[T any] struct {
    Data T `json:"data,omitempty"` // struct 类型不会因为零值被省略
}

12. 最佳实践

10.1 何时使用泛型

✅ 应该使用泛型 ❌ 不应该使用泛型
类型安全的容器(Set、Stack、Tree) 只有一两种类型的场景
操作 slice/map 的工具函数 每个类型的行为差异很大
通用算法(排序、搜索、去重) 用接口就能清晰表达
减少代码重复 泛型让代码更难读

10.2 设计原则

// ✅ GOOD: 约束最小化 — 只约束你真正需要的
func Contains[E comparable](s []E, v E) bool {
    for _, e := range s {
        if e == v {
            return true
        }
    }
    return false
}
// 只要求 comparable(== 操作),不要求更多

// ❌ BAD: 过度约束
func ContainsBad[E constraints.Ordered](s []E, v E) bool {
    // 只需要 ==,却约束了 Ordered(<、>、<=、>= 都要求了)
    for _, e := range s {
        if e == v {
            return true
        }
    }
    return false
}

// ✅ GOOD: 单一职责 — 一个类型参数就够
func First[T any](s []T) T { return s[0] }

// ⚠️ 谨慎:多类型参数 — 确实需要时再用
func Convert[From, To any](f From, converter func(From) To) To {
    return converter(f)
}

10.3 命名清晰

// ✅ 语义清晰的命名
func Merge[K comparable, V any](a, b map[K]V) map[K]V { ... }
func Transform[T, U any](s []T, fn func(T) U) []U { ... }

// ❌ 不清晰的命名
func F[A, B, C any](a A, b B) C { ... }  // A、B、C 是什么?

13. 泛型与现有代码的互操作

11.1 从 interface{} 迁移到泛型

// 旧代码:interface{}
func ContainsOld(s interface{}, v interface{}) bool {
    // 需要反射,运行时才知道类型,容易出错
    slice := reflect.ValueOf(s)
    for i := 0; i < slice.Len(); i++ {
        if reflect.DeepEqual(slice.Index(i).Interface(), v) {
            return true
        }
    }
    return false
}

// 新代码:泛型
func ContainsNew[E comparable](s []E, v E) bool {
    for _, e := range s {
        if e == v {
            return true
        }
    }
    return false
}

11.2 渐进式采用

泛型可以与旧代码共存,不需要一次性重写所有代码:

// 旧的非泛型函数可以调用泛型函数
func findUser(names []string, target string) int {
    return slices.Index(names, target) // 自动用 slices.Index[string]
}

// 泛型函数可以接收旧的非泛型类型
type LegacyStack struct { items []interface{} }
// 可以新写一个泛型包装器
func NewStack[T any]() *Stack[T] { return &Stack[T]{} }

14. 相关资源