SuperAPI AI 工具集

go-generics

编程与开发

查看项目

更新于 10-14 11:06

技能介绍

Go Generics and Type Parameters(Go 泛型与类型参数)是一份关于在 Go 语言中使用泛型的技能资料。

适用条件

泛型需要 Go 1.18 及以上版本。当需要判断是否使用 Go 泛型、编写泛型函数或类型、选择约束,或在类型别名与类型定义之间取舍时,可参考本资料。

资源路由

references/CONSTRAINTS.md:在组合约束、使用类型集,或在泛型与接口之间选择时阅读。

何时使用泛型

基本原则:先使用具体类型,只有当第二种类型出现时才进行泛化。

优先使用泛型的场景:

  • 多种类型共享完全相同的逻辑(如排序、过滤、map/reduce)。
  • 否则需要依赖 any 和大量类型切换。
  • 正在构建可复用的数据结构(如并发安全集合、有序 map)。

避免使用泛型的场景:

  • 实践中只会实例化一种类型。
  • 接口已经能清晰地建模共享行为。
  • 泛型代码比针对具体类型的替代方案更难阅读。

引用:“Write code, don't design types.” —— Robert Griesemer 与 Ian Lance Taylor。

决策流程

多种类型是否共享完全相同的逻辑?

  • 否 → 使用具体类型
  • 是 → 它们是否共享一个有用的接口?
    • 是 → 使用接口
    • 否 → 使用泛型

示例(过早泛型,实际只用 int 调用):

func Sum[T constraints.Integer | constraints.Float](vals []T) T { var total T for _, v := range vals { total += v } return total }

推荐写法:

func SumInts(vals []int) int { var total int for _, v := range vals { total += v } return total }

类型参数命名

T:通用类型参数;K:map 键类型;V:map 值类型;E:元素/条目类型。

对于复杂约束,也可使用简短描述性名称,例如:

func Marshal[Opts encoding.MarshalOptions](v any, opts Opts) ([]byte, error)

类型别名与类型定义

类型别名(type Old = new.Name)较少使用,仅用于包迁移或渐进式 API 重构。

约束组合

使用 ~(底层类型)与 |(并集)组合约束:

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

func Sum[T Numeric](vals []T) T { var total T for _, v := range vals { total += v } return total }

对于 cmp.Ordered 等标准约束,应使用 constraints 包或 cmp 包(Go 1.21+),而不是自行编写。

常见误区

不要包装标准库类型:

// 不好:泛型包装增加复杂度却无价值 type Set[T comparable] struct { m map[T]struct{} }

// 更好:用法简单时直接使用 map[T]struct{} seen := map[string]struct{}{}

当泛型能消除多个调用点的重复时,才值得其复杂度。单次使用的泛型只是间接层。

不要用泛型满足接口:

// 不好:T 只是用来满足接口 —— 直接用接口即可 func Process[T io.Reader](r T) error { ... }

// 好:直接接受接口 func Process(r io.Reader) error { ... }

避免过度约束:

// 不好:约束比需要的更严格 func Contains[T interface{ ~int | ~string }](slice []T, target T) bool { ... }

// 好:comparable 已足够 func Contains[T comparable](slice []T, target T) bool { ... }

快速参考

  • 何时使用泛型:仅当多种类型共享完全相同的逻辑且接口不足够时。
  • 起点:先写具体代码,之后再泛化。
  • 命名:单个大写字母(T、K、V、E)。
  • 类型别名:同一类型的替代名称;仅用于迁移。
  • 约束组合:使用 ~ 表示底层类型,使用 | 表示并集。
  • 常见误区:不要泛化单次使用的代码,或在接口足够时使用泛型。

相关技能

  • 接口与泛型:在判断接口是否已能建模共享行为而无需泛型时,参见 go-interfaces。
  • 类型声明:在定义新类型、类型别名,或在类型定义与别名之间选择时,参见 go-declarations。
  • 记录泛型 API:在为泛型函数编写文档注释和可运行示例时,参见 go-documentation。
  • 命名类型参数:在为类型参数或约束接口选择名称时,参见 go-naming。