go-generics

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Generics and Type Parameters

Go泛型与类型参数



When to Use Generics

何时使用泛型

Start with concrete types. Generalize only when a second type appears.
先从具体类型开始编写代码。只有当需要支持第二种类型时,再进行泛化处理。

Prefer Generics When

优先使用泛型的场景

  • Multiple types share identical logic (sorting, filtering, map/reduce)
  • You would otherwise rely on
    any
    and excessive type switching
  • You are building a reusable data structure (concurrent-safe set, ordered map)
  • 多种类型共享完全相同的逻辑(排序、过滤、映射/归约)
  • 否则需要依赖
    any
    类型并进行大量类型判断
  • 构建可复用的数据结构(并发安全集合、有序映射)

Avoid Generics When

避免使用泛型的场景

  • Only one type is being instantiated in practice
  • Interfaces already model the shared behavior cleanly
  • The generic code is harder to read than the type-specific alternative
"Write code, don't design types." — Robert Griesemer and Ian Lance Taylor
  • 实际应用中只会实例化一种类型
  • 接口已能清晰地对共享行为进行建模
  • 泛型代码比特定类型的替代代码更难阅读
"先写代码,再设计类型。" — Robert Griesemer 和 Ian Lance Taylor

Decision Flow

决策流程

Do multiple types share identical logic?
├─ No  → Use concrete types
├─ Yes → Do they share a useful interface?
│        ├─ Yes → Use an interface
│        └─ No  → Use generics
Bad:
go
// 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
}
Good:
go
func SumInts(vals []int) int {
    var total int
    for _, v := range vals {
        total += v
    }
    return total
}

多种类型是否共享完全相同的逻辑?
├─ 否  → 使用具体类型
├─ 是 → 它们是否共享一个实用的接口?
│        ├─ 是 → 使用接口
│        └─ 否  → 使用泛型
不良示例:
go
// 过早泛型:实际上只会以int类型调用
func Sum[T constraints.Integer | constraints.Float](vals []T) T {
    var total T
    for _, v := range vals {
        total += v
    }
    return total
}
推荐示例:
go
func SumInts(vals []int) int {
    var total int
    for _, v := range vals {
        total += v
    }
    return total
}

Type Parameter Naming

类型参数命名

NameTypical Use
T
General type parameter
K
Map key type
V
Map value type
E
Element/item type
For complex constraints, a short descriptive name is acceptable:
go
func Marshal[Opts encoding.MarshalOptions](v any, opts Opts) ([]byte, error)

名称典型用途
T
通用类型参数
K
映射键类型
V
映射值类型
E
元素/项类型
对于复杂的约束条件,可使用简短的描述性名称:
go
func Marshal[Opts encoding.MarshalOptions](v any, opts Opts) ([]byte, error)

Type Aliases vs Type Definitions

类型别名与类型定义

Type aliases (
type Old = new.Name
) are rare — use only for package migration or gradual API refactoring.

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

Constraint Composition

约束组合

Combine constraints with
~
(underlying type) and
|
(union):
go
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
}
Use the
constraints
package or
cmp
package (Go 1.21+) for standard constraints like
cmp.Ordered
instead of writing your own.
Read references/CONSTRAINTS.md when writing custom type constraints, composing constraints with ~ and |, or debugging type inference issues.

使用
~
(底层类型)和
|
(联合类型)组合约束条件:
go
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+),而非自行编写。
当编写自定义类型约束、使用~和|组合约束条件,或调试类型推断问题时,请阅读references/CONSTRAINTS.md。

Common Pitfalls

常见误区

Don't Wrap Standard Library Types

不要封装标准库类型

go
// 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{}{}
Generics justify their complexity when they eliminate duplication across multiple call sites. A single-use generic is just indirection.
go
// 不良示例:泛型封装增加了复杂度却没有实际价值
type Set[T comparable] struct {
    m map[T]struct{}
}

// 推荐:当使用场景简单时,直接使用map[T]struct{}
seen := map[string]struct{}{}
只有当泛型能消除多个调用点的重复代码时,其复杂度才是合理的。仅单次使用的泛型只是增加了一层间接性。

Don't Use Generics for Interface Satisfaction

不要为了满足接口而使用泛型

go
// 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 { ... }
go
// 不良示例:T仅用于满足接口——直接使用接口即可
func Process[T io.Reader](r T) error { ... }

// 推荐:直接接收接口类型
func Process(r io.Reader) error { ... }

Avoid Over-Constraining

避免过度约束

go
// 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 { ... }

go
// 不良示例:约束条件比实际需求更严格
func Contains[T interface{ ~int | ~string }](slice []T, target T) bool { ... }

// 推荐:使用comparable约束已足够
func Contains[T comparable](slice []T, target T) bool { ... }

Quick Reference

快速参考

TopicGuidance
When to use genericsOnly when multiple types share identical logic and interfaces don't suffice
Starting pointWrite concrete code first; generalize later
NamingSingle uppercase letter (
T
,
K
,
V
,
E
)
Type aliasesSame type, alternate name; use only for migration
Constraint compositionUse
~
for underlying types, `
Common pitfallDon't genericize single-use code or when interfaces suffice

主题指导原则
何时使用泛型仅当多种类型共享完全相同的逻辑且接口无法满足需求时使用
初始方案先编写具体代码,再进行泛化
命名规则使用单个大写字母(
T
K
V
E
类型别名与原类型相同,仅为别名;仅用于迁移场景
约束组合使用
~
表示底层类型,`
常见误区不要对单次使用的代码泛化,接口能满足需求时也不要使用泛型

Related Skills

相关技能

  • Interfaces vs generics: See go-interfaces when deciding whether an interface already models the shared behavior without generics
  • Type declarations: See go-declarations when defining new types, type aliases, or choosing between type definitions and aliases
  • Documenting generic APIs: See go-documentation when writing doc comments and runnable examples for generic functions
  • Naming type parameters: See go-naming when choosing names for type parameters or constraint interfaces
  • 接口 vs 泛型:当判断接口是否已能在不使用泛型的情况下对共享行为建模时,请查看go-interfaces
  • 类型声明:当定义新类型、类型别名,或在类型定义与别名之间做选择时,请查看go-declarations
  • 泛型API文档编写:当为泛型函数编写文档注释和可运行示例时,请查看go-documentation
  • 类型参数命名:当为类型参数或约束接口选择名称时,请查看go-naming