go-generics
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo 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 and excessive type switching
any - 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 genericsBad:
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
类型参数命名
| Name | Typical Use |
|---|---|
| General type parameter |
| Map key type |
| Map value type |
| Element/item type |
For complex constraints, a short descriptive name is acceptable:
go
func Marshal[Opts encoding.MarshalOptions](v any, opts Opts) ([]byte, error)| 名称 | 典型用途 |
|---|---|
| 通用类型参数 |
| 映射键类型 |
| 映射值类型 |
| 元素/项类型 |
对于复杂的约束条件,可使用简短的描述性名称:
go
func Marshal[Opts encoding.MarshalOptions](v any, opts Opts) ([]byte, error)Type Aliases vs Type Definitions
类型别名与类型定义
Type aliases () are rare — use only for package migration
or gradual API refactoring.
type Old = new.Name类型别名()的使用场景很少——仅用于包迁移或渐进式API重构。
type Old = new.NameConstraint 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 package or package (Go 1.21+) for standard constraints
like instead of writing your own.
constraintscmpcmp.OrderedRead 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
}对于这类标准约束条件,建议使用包或包(Go 1.21+),而非自行编写。
cmp.Orderedconstraintscmp当编写自定义类型约束、使用~和|组合约束条件,或调试类型推断问题时,请阅读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
快速参考
| Topic | Guidance |
|---|---|
| When to use generics | Only when multiple types share identical logic and interfaces don't suffice |
| Starting point | Write concrete code first; generalize later |
| Naming | Single uppercase letter ( |
| Type aliases | Same type, alternate name; use only for migration |
| Constraint composition | Use |
| Common pitfall | Don't genericize single-use code or when interfaces suffice |
| 主题 | 指导原则 |
|---|---|
| 何时使用泛型 | 仅当多种类型共享完全相同的逻辑且接口无法满足需求时使用 |
| 初始方案 | 先编写具体代码,再进行泛化 |
| 命名规则 | 使用单个大写字母( |
| 类型别名 | 与原类型相同,仅为别名;仅用于迁移场景 |
| 约束组合 | 使用 |
| 常见误区 | 不要对单次使用的代码泛化,接口能满足需求时也不要使用泛型 |
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