泛型

翻译整理自 Tutorial: Getting started with generics 与 Generics Proposal。

Go 1.18 引入泛型(Generics),让函数和类型可以接受类型参数,在编译期生成类型安全的代码。

1. 类型参数

func Print[T any](s []T) {
    for _, v := range s {
        fmt.Println(v)
    }
}

Print[int]([]int{1, 2, 3})    // 显式指定
Print([]string{"a", "b"})     // 推断

[T any] 中 any 是约束(constraint),等价于 interface{}。

2. 类型约束:comparable

需要比较相等用 comparable:

func Index[T comparable](s []T, x T) int {
    for i, v := range s {
        if v == x {
            return i
        }
    }
    return -1
}

comparable 是预声明的约束,表示类型支持 == 与 !=。

3. 自定义约束

golang.org/x/exp/constraints(或自己定义):

type Number interface {
    int | int8 | int16 | int32 | int64 |
    float32 | float64
}

func Sum[T Number](nums []T) T {
    var sum T
    for _, v := range nums {
        sum += v
    }
    return sum
}

Sum([]int{1, 2, 3})           // 6
Sum([]float64{1.5, 2.5})      // 4.0

~T 表示底层类型

type Number interface {
    ~int | ~float64
}

type MyInt int
var x MyInt = 10
// Sum([]MyInt{x}) 也能编译,因为有 ~

4. 泛型类型

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) {
    var zero T
    if len(s.items) == 0 {
        return zero, false
    }
    v := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return v, true
}

5. 多类型参数

func Map[T, U any](in []T, f func(T) U) []U {
    out := make([]U, len(in))
    for i, v := range in {
        out[i] = f(v)
    }
    return out
}

doubled := Map([]int{1, 2, 3}, func(v int) int { return v * 2 })
strs   := Map([]int{1, 2}, strconv.Itoa)

6. 约束组合与嵌入

type Stringer interface {
    String() string
}

type Constraint interface {
    Number
    Stringer
}

7. 标准库泛型包

Go 1.21+ 提供以下泛型容器包:

  • slices:泛型切片操作(Sort、Contains、Reverse)
  • maps:泛型 map 操作(Copy、Keys、Values)
  • golang.org/x/exp:扩展包,包含 constraints、slices 早期版本
s := []int{3, 1, 2}
slices.Sort(s)
fmt.Println(slices.Contains(s, 1))

8. 使用建议

  • 优先具体类型,必要时再用泛型。Go 强调简洁,泛型不是银弹。
  • 用于通用数据结构(栈、队列、树、map 工具)最有价值。
  • 不要把泛型当反射用,性能与可读性都不一定更好。
  • 在 go vet 中检查泛型代码是否符合约束。

小结

泛型让 Go 在保留简洁的同时获得了类型安全的复用能力。建议先用、再优化,把它当作可选工具。