Go 泛型实战:用 constraints 写出可复用的容器与工具

test12026-09-140 次阅读

何时该上泛型

当你发现自己在为 int、string、自定义结构体各写一遍几乎相同的逻辑时,就是泛型出场的时候。泛型的价值是“一套实现,多种类型”,而不是为了炫技。Go 1.18 引入泛型后,容器、工具函数、仓储接口都能写得又安全又简洁。

一个类型安全的栈

package main

import "fmt"

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
    }
    last := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return last, true
}

func main() {
    var s Stack[int]
    s.Push(1)
    s.Push(2)
    v, ok := s.Pop()
    fmt.Println(v, ok) // 输出:2 true
}

复用逻辑:取 map 的 key 集合

这种“对任意 K/V 都一样的遍历逻辑”最适合写成泛型函数,避免每次手写循环。

func MapKeys[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
}

// 调用处类型自动推断
keys := MapKeys(map[string]int{"a": 1, "b": 2}) // []string{"a", "b"}

用 constraints 约束数值类型

Go 1.21 起标准库 cmp 包提供了 cmp.Ordered,可以直接约束“可比较大小”的类型,用来写通用的求和、最值函数。

import "cmp"

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

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

泛型仓储接口的思路

用泛型定义仓储契约,不同实体各自实现,既统一了方法签名,又不丢失具体类型。

type Repository[T any] interface {
    FindByID(id int64) (T, error)
    Save(entity T) error
}

type UserRepo struct{ db *sql.DB }

func (r *UserRepo) FindByID(id int64) (User, error) { /* ... */ }
func (r *UserRepo) Save(u User) error               { /* ... */ }

// UserRepo 自动满足 Repository[User]

经验清单

  • 泛型解决“类型不同、逻辑相同”,接口解决“行为相同、实现各异”,两者互补。
  • 不要为每一个小函数都加泛型,只有真正跨类型复用时才值得。
  • 标准库 cmp.Ordered / constraints 比自己手写一堆类型分支更可靠。
  • 泛型让编译期类型检查更严,等于把一部分运行时错误提前消灭。
T

test1

文章作者

何时该上泛型 当你发现自己在为 int、string、自定义结构体各写一遍几乎相同的逻辑时,就是泛型出场的时候。泛型的...

分类
技术
发布时间
2026-09-14
字数
约 1752 字
阅读
0 次