Files
OpenFlare/.agents/skills/go-generics/SKILL.md
T
2026-08-04 11:40:32 +08:00

4.7 KiB
Raw Blame History

name, description, license, compatibility, metadata
name description license compatibility metadata
go-generics Use when deciding whether to use Go generics, writing generic functions or types, choosing constraints, or picking between type aliases and type definitions. Also use when a user is writing a utility function that could work with multiple types, even if they don't mention generics explicitly. Does not cover interface design without generics (see go-interfaces). Apache-2.0 Requires Go 1.18+ (generics were introduced in Go 1.18)
sources
Google Style Guide

Go 泛型与类型参数


何时使用泛型

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

优先使用泛型的场景

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

避免使用泛型的场景

  • 实践中只有一种类型被实例化
  • 接口已经能清晰地表达共享行为
  • 泛型代码比特定类型的替代方案更难阅读

"写代码,不要设计类型。"—— 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 映射键类型
V 映射值类型
E 元素/项目类型

对于复杂约束,可以使用简短的描述性名称:

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

类型别名 vs 类型定义

类型别名(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
}

使用 constraints 包或 cmp 包(Go 1.21+)中的标准约束如 cmp.Ordered,而不是自己编写。

在编写自定义类型约束、使用 ~ 和 | 组合约束或调试类型推断问题时,阅读 references/CONSTRAINTS.md。


常见陷阱

不要包装标准库类型

// 不好:泛型包装器增加了复杂度但没有价值
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)
类型别名 相同类型,替代名称;仅用于迁移
约束组合 使用 ~ 表示底层类型,`
常见陷阱 不要对单次使用的代码或接口已足够时使用泛型

相关技能

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