Go 1.18 引入的泛型(Generics)是 Go 历史上最具革命性的特性。它让代码复用和类型安全达到了新高度。

基本语法

泛型函数

// T 是类型参数,any 是约束(表示任意类型)
func Map[T, U any](s []T, f func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s {
        result[i] = f(v)
    }
    return result
}

// 使用
ints := []int{1, 2, 3}
strings := Map(ints, func(i int) string {
    return fmt.Sprintf("#%d", i)
})
// strings: []string{"#1", "#2", "#3"}

泛型类型

type Stack[T any] struct {
    items []T
}

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

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

// 使用
intStack := &Stack[int]{}
intStack.Push(42)
v, ok := intStack.Pop()  // v 是 int 类型

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

类型参数推断

Go 1.18+ 支持从函数参数推断类型参数:

// 可以省略类型参数
strings := Map(ints, func(i int) string {
    return fmt.Sprintf("#%d", i)
})
// 等价于
strings := Map[int, string](ints, func(i int) string {
    return fmt.Sprintf("#%d", i)
})

类型约束(Constraints)

约束定义了类型参数必须满足的要求。

内置约束

func Identity[T any](v T) T { return v }

// comparable: 可比较的类型(可用 == 和 !=)
func Index[T comparable](s []T, v T) int {
    for i, item := range s {
        if item == v {
            return i
        }
    }
    return -1
}

自定义约束

约束本质上是接口:

// 定义约束
type Number interface {
    ~int | ~int64 | ~float64 | ~float32
}

// ~ 表示底层类型匹配即可(包括基于这些类型的别名)
type MyInt int

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

// 使用
var a MyInt = 10
var b MyInt = 20
c := Add(a, b)  // c 是 MyInt 类型

约束组合

type Stringer interface {
    String() string
}

// 同时要求 comparable 和 String() 方法
type Printable interface {
    comparable
    Stringer
}

func PrintEqual[T Printable](a, b T) {
    if a == b {
        fmt.Println(a.String())
    }
}

cmp 包

标准库 cmp 包(Go 1.21+)提供了有序类型的约束:

import "cmp"

// cmp.Ordered: 支持 < > <= >= 比较的类型
func Min[T cmp.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

注意:cmp 包只包含 Ordered 约束和 Compare 函数。如需更多约束(如 SignedUnsignedInteger 等),可使用 golang.org/x/exp/constraints

实用示例

1. 泛型链表

type Node[T any] struct {
    Value T
    Next  *Node[T]
}

type LinkedList[T any] struct {
    Head *Node[T]
}

func (l *LinkedList[T]) Prepend(v T) {
    l.Head = &Node[T]{Value: v, Next: l.Head}
}

func (l *LinkedList[T]) Find(f func(T) bool) *Node[T] {
    for n := l.Head; n != nil; n = n.Next {
        if f(n.Value) {
            return n
        }
    }
    return nil
}

2. 泛型 Map 工具

// 过滤
func Filter[T any](s []T, f func(T) bool) []T {
    result := make([]T, 0)
    for _, v := range s {
        if f(v) {
            result = append(result, v)
        }
    }
    return result
}

// 归约
func Reduce[T any, U any](s []T, init U, f func(U, T) U) U {
    result := init
    for _, v := range s {
        result = f(result, v)
    }
    return result
}

// 使用
nums := []int{1, 2, 3, 4, 5, 6}
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
sum := Reduce(evens, 0, func(a, b int) int { return a + b })

3. 泛型单例

type Singleton[T any] struct {
    instance *T
    once     sync.Once
}

func (s *Singleton[T]) Get(f func() *T) *T {
    s.once.Do(func() {
        s.instance = f()
    })
    return s.instance
}

4. 泛型 Repository 模式

type Repository[T any] interface {
    Get(id string) (*T, error)
    List() ([]*T, error)
    Create(t *T) error
    Update(t *T) error
    Delete(id string) error
}

type User struct {
    ID   string
    Name string
}

// 可以直接使用泛型实现
type InMemoryRepo[T any] struct {
    data map[string]*T
}

func NewInMemoryRepo[T any]() *InMemoryRepo[T] {
    return &InMemoryRepo[T]{data: make(map[string]*T)}
}

高级技巧

1. 类型约束的巧妙用法

// 限制只能是特定结构体的指针
type Entity interface {
    *User | *Product | *Order
    GetID() string
}

func Save[T Entity](e T) error {
    id := e.GetID()
    // ...
}

2. 泛型方法

Go 不支持泛型方法,只能在泛型类型上定义方法:

// 错误:方法不能有类型参数
type Box struct{}
func (b Box) Map[T any](f func(T) T) T { }  // 编译错误

// 正确:类型必须是泛型的
type Box[T any] struct {
    value T
}
func (b Box[T]) Map(f func(T) T) Box[T] {
    return Box[T]{value: f(b.value)}
}

3. 泛型与接口

泛型类型可以实现接口:

type Container interface {
    Len() int
}

type Box[T any] struct {
    items []T
}

func (b *Box[T]) Len() int { return len(b.items) }

// Box[T] 实现了 Container 接口
var _ Container = (*Box[int])(nil)

常见陷阱

1. 零值问题

func First[T any](s []T) T {
    if len(s) == 0 {
        var zero T  // 返回类型的零值
        return zero
    }
    return s[0]
}

// 更好的做法是返回可选值
func SafeFirst[T any](s []T) (T, bool) {
    if len(s) == 0 {
        var zero T
        return zero, false
    }
    return s[0], true
}

2. 约束过松

// 太松:任何类型都可以,但函数内部用了 + 操作
func Add[T any](a, b T) T {
    return a + b  // 编译错误!
}

// 正确:使用约束限制
func Add[T Number](a, b T) T {
    return a + b
}

3. 约束过紧

// 过紧:只接受 int
func Double(s []int) []int {
    result := make([]int, len(s))
    for i, v := range s {
        result[i] = v * 2
    }
    return result
}

// 更好:使用泛型
func Double[T Number](s []T) []T {
    result := make([]T, len(s))
    for i, v := range s {
        result[i] = v * 2
    }
    return result
}

4. 滥用泛型

不是所有地方都需要泛型:

// 不需要泛型:简单场景
func PrintInts(s []int) {
    for _, v := range s {
        fmt.Println(v)
    }
}

// 过度设计
func PrintSlice[T any](s []T) {
    for _, v := range s {
        fmt.Println(v)
    }
}

// 只有当你需要处理多种类型且保持类型安全时,才用泛型

性能考虑

1. 编译时特化

Go 编译器会为每个具体类型生成特化代码:

// 编译器会生成两份代码:
ints := Map([]int{1, 2, 3}, func(i int) int { return i * 2 })
strings := Map([]string{"a", "b"}, func(s string) string { return s + "!" })
// 一份用于 int,一份用于 string

2. 避免不必要的泛型

对于性能敏感且类型固定的场景,直接使用具体类型:

// 泛型版本(有轻微开销)
func Sum[T Number](s []T) T {
    var sum T
    for _, v := range s {
        sum += v
    }
    return sum
}

// 具体类型版本(更快)
func SumInt64(s []int64) int64 {
    var sum int64
    for _, v := range s {
        sum += v
    }
    return sum
}

3. 内存布局

泛型类型的内存布局与具体类型相同,没有额外开销。

最佳实践

1. 优先使用标准库

Go 1.21+ 标准库提供了很多泛型工具:

import (
    "slices"
    "maps"
)

// 排序
slices.Sort([]int{3, 1, 2})

// 查找
idx := slices.Index([]string{"a", "b"}, "b")

// Map 克隆
m2 := maps.Clone(m1)

2. 约束要恰到好处

// 太松
func Process[T any](v T) { }

// 太紧
func Process[T int | int64](v T) { }

// 恰到好处
func Process[T Number](v T) { }

3. 文档要清晰

// Sort 对切片进行排序
// T 必须是有序类型(支持 < 比较)
func Sort[T cmp.Ordered](s []T) {
    // ...
}

4. 测试要覆盖多种类型

func TestMin(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"ints", 1, 2, 1},
        {"negative", -1, -2, -2},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Min(tt.a, tt.b); got != tt.want {
                t.Errorf("Min(%v, %v) = %v, want %v", tt.a, tt.b, got, tt.want)
            }
        })
    }
}

总结

Go 泛型的核心价值:

特性收益
类型参数代码复用,减少重复
类型约束编译期类型安全
类型推断使用简洁
编译时特化运行时零开销

使用原则:

  1. 需要处理多种类型且保持类型安全 → 用泛型
  2. 类型固定或只需要一种类型 → 不用泛型
  3. 需要 interface{} + 类型断言 → 优先考虑泛型
  4. 性能敏感且类型固定 → 用具体类型