mcpField
G
go-generics

go-generics

github.com/cxuu开发1861.0k 次安装19 周前更新
代码仓库

关于 go-generics

面向 Go 开发者的泛型技能,帮助判断何时以及如何使用类型参数。它建议先编写具体代码,仅在共享逻辑确有需要时再泛化。

主要功能

  1. 01判断使用泛型还是接口
  2. 02使用联合与底层类型组合约束
  3. 03类型参数命名规范
  4. 04常见问题与标准约束

适用场景

  1. 01判断重复逻辑是否适合泛型
  2. 02为可复用 Go 函数设计约束
  3. 03检查泛型代码是否引入不必要复杂度

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cxuu/golang-skills --skill go-generics

安装后在 Claude Code 中输入/go-generics确认已加载。

附带文件references/CONSTRAINTS.md

详细介绍

Go 泛型与类型参数

兼容性:泛型要求 Go 1.18+。

资源路由

  • references/CONSTRAINTS.md - 在编写约束、使用类型集或在泛型与接口之间进行选择时阅读。

何时使用泛型

从具体类型开始。只有在出现第二种类型时才进行泛化。

优先使用泛型的情况
  • 多种类型共享相同的逻辑(排序、筛选、映射/归约)
  • 否则你会依赖 any 和过多的类型切换
  • 你正在构建可复用的数据结构(并发安全的集合、有序映射)
避免使用泛型的情况
  • 实际上只会实例化一种类型
  • 接口已经能清晰地建模共享行为
  • 泛型代码比特定类型的替代方案更难阅读

“编写代码,不要设计类型。”——Robert Griesemer 和 Ian Lance Taylor

决策流程
Do multiple types share identical logic?
├─ No  → Use concrete types
├─ Yes → Do they share a useful interface?
│        ├─ Yes → Use an interface
│        └─ No  → Use generics

不佳:

// Premature generics: only ever called with 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映射键类型
V映射值类型
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+),而不是自行编写。


常见陷阱

不要封装标准库类型
// Bad: generic wrapper adds complexity without value
type Set[T comparable] struct {
    m map[T]struct{}
}

// Better: use map[T]struct{} directly when the usage is simple
seen := map[string]struct{}{}

只有在多个调用点消除重复时,泛型才值得其带来的复杂性。只使用一次的泛型不过是多了一层间接。

不要为了满足接口而使用泛型
// Bad: T is only used to satisfy an interface — just use the interface
func Process[T io.Reader](r T) error { ... }

// Good: accept the interface directly
func Process(r io.Reader) error { ... }
避免过度约束
// Bad: constraint is more restrictive than needed
func Contains[T interface{ ~int | ~string }](slice []T, target T) bool { ... }

// Good: comparable is sufficient
func Contains[T comparable](slice []T, target T) bool { ... }

快速参考

主题指引
何时使用泛型仅当多种类型共享相同逻辑且接口无法满足需求时
起点先编写具体代码;之后再泛化
命名单个大写字母(T、K、V、E)
类型别名同一类型的另一个名称;仅用于迁移
约束组合使用 ~ 表示底层类型,使用 `
常见陷阱不要将只使用一次的代码泛型化,也不要在接口足以满足需求时使用泛型

相关技能

  • 接口与泛型:在判断接口是否已能在不使用泛型的情况下建模共享行为时,请参阅 go-interfaces
  • 类型声明:在定义新类型、类型别名,或在类型定义与类型别名之间进行选择时,请参阅 go-declarations
  • 编写泛型 API 文档:在为泛型函数编写文档注释和可运行示例时,请参阅 go-documentation
  • 类型参数命名:在为类型参数或约束接口选择名称时,请参阅 go-naming

评论

支持 Markdown