目录

Go 1.18 泛型:从入门到实践

前言

2022 年 3 月,Go 1.18 正式发布,其中最引人瞩目的特性就是泛型(Generics)。这是 Go 语言自 2009 年开源以来最大的一次语法变更,解决了长期被开发者诟病的"没有泛型"的痛点。

为什么需要泛型?

在没有泛型之前,如果我们要写一个反转切片的函数,需要为每种类型写一份:

func ReverseInts(s []int) []int {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
    return s
}

func ReverseStrings(s []string) []string {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
    return s
}

使用泛型后,只需一个函数就能搞定:

func Reverse[T any](s []T) []T {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
    return s
}

泛型语法速览

1. 泛型函数

// 任意类型,T 必须是可比较的
func Find[T comparable](items []T, target T) int {
    for i, v := range items {
        if v == target {
            return i
        }
    }
    return -1
}

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
}

3. 类型约束(constraints)

// 数值类型约束
type Number interface {
    ~int | ~int64 | ~float64
}

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

Go 1.18 内置了 constraints 包(实验性),包含 constraints.Orderedconstraints.Integer 等常用约束。Go 1.21 起移到了 cmpmath 等标准包中。

实际应用场景

场景一:通用的 JSON 响应封装

type ApiResponse[T any] struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Data    T      `json:"data"`
}

// 使用
func GetUser(w http.ResponseWriter, r *http.Request) {
    user := User{Name: "张三", Age: 28}
    resp := ApiResponse[User]{
        Code:    200,
        Message: "success",
        Data:    user,
    }
    json.NewEncoder(w).Encode(resp)
}

场景二:类型安全的集合操作

// Map 转换
func Map[T, U any](items []T, fn func(T) U) []U {
    result := make([]U, len(items))
    for i, item := range items {
        result[i] = fn(item)
    }
    return result
}

// Filter 过滤
func Filter[T any](items []T, fn func(T) bool) []T {
    var result []T
    for _, item := range items {
        if fn(item) {
            result = append(result, item)
        }
    }
    return result
}

// 使用示例
names := []string{"alice", "bob", "charlie"}
upperNames := Map(names, strings.ToUpper)
// ["ALICE", "BOB", "CHARLIE"]

longNames := Filter(names, func(s string) bool {
    return len(s) > 3
})
// ["alice", "charlie"]

注意事项

  1. 性能:泛型不会引入运行时开销,编译器会在编译期对每个具体类型生成特化代码
  2. 限制:泛型类型不能用于方法(receiver 不能是泛型),但结构体方法可以用
  3. 可读性:不要过度使用泛型,简单的 interface{} + 类型断言有时更清晰

总结

Go 泛型的引入让代码复用变得更加优雅,尤其是在编写通用数据结构和工具函数时。推荐的使用原则是:“需要的时候再用,不要为了泛型而泛型”。对于日常业务开发,大部分场景其实用不到泛型,但当遇到重复的类型书写的痛点时,泛型就是最好的解药。