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

174 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: go-generics
description: 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).
license: Apache-2.0
compatibility: Requires Go 1.18+ (generics were introduced in Go 1.18)
metadata:
sources: "Google Style Guide"
---
# Go 泛型与类型参数
---
## 何时使用泛型
从具体类型开始。只在出现第二种类型时才进行泛化。
### 优先使用泛型的场景
- 多种类型共享相同的逻辑(排序、过滤、map/reduce)
- 否则需要依赖 `any` 和大量的类型切换
- 正在构建可复用的数据结构(并发安全的集合、有序映射)
### 避免使用泛型的场景
- 实践中只有一种类型被实例化
- 接口已经能清晰地表达共享行为
- 泛型代码比特定类型的替代方案更难阅读
> "写代码,不要设计类型。"—— Robert Griesemer 和 Ian Lance Taylor
### 决策流程
```
多种类型是否共享相同的逻辑?
├─ 否 → 使用具体类型
├─ 是 → 它们是否共享一个有用的接口?
│ ├─ 是 → 使用接口
│ └─ 否 → 使用泛型
```
**不好:**
```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
}
```
---
## 类型参数命名
| 名称 | 典型用途 |
|------|----------|
| `T` | 通用类型参数 |
| `K` | 映射键类型 |
| `V` | 映射值类型 |
| `E` | 元素/项目类型 |
对于复杂约束,可以使用简短的描述性名称:
```go
func Marshal[Opts encoding.MarshalOptions](v any, opts Opts) ([]byte, error)
```
---
## 类型别名 vs 类型定义
类型别名(`type Old = new.Name`)很少使用——仅用于包迁移或渐进式 API 重构。
---
## 约束组合
使用 `~`(底层类型)和 `|`(联合)组合约束:
```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
}
```
使用 `constraints` 包或 `cmp` 包(Go 1.21+)中的标准约束如 `cmp.Ordered`,而不是自己编写。
> 在编写自定义类型约束、使用 ~ 和 | 组合约束或调试类型推断问题时,阅读 [references/CONSTRAINTS.md](references/CONSTRAINTS.md)。
---
## 常见陷阱
### 不要包装标准库类型
```go
// 不好:泛型包装器增加了复杂度但没有价值
type Set[T comparable] struct {
m map[T]struct{}
}
// 更好:当用法简单时直接使用 map[T]struct{}
seen := map[string]struct{}{}
```
泛型在消除**多个调用点**之间的重复时才能证明其复杂度的合理性。单次使用的泛型只是多余的间接层。
### 不要为接口满足而使用泛型
```go
// 不好:T 仅用于满足接口——直接使用接口即可
func Process[T io.Reader](r T) error { ... }
// 好:直接接受接口
func Process(r io.Reader) error { ... }
```
### 避免过度约束
```go
// 不好:约束比需要的更严格
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`) |
| 类型别名 | 相同类型,替代名称;仅用于迁移 |
| 约束组合 | 使用 `~` 表示底层类型,`|` 表示联合;优先使用 `cmp.Ordered` 而非自定义 |
| 常见陷阱 | 不要对单次使用的代码或接口已足够时使用泛型 |
---
## 相关技能
- **接口 vs 泛型**:在决定接口是否已经能表达共享行为而无需泛型时,参见 [go-interfaces](../go-interfaces/SKILL.md)
- **类型声明**:在定义新类型、类型别名或在类型定义和别名之间选择时,参见 [go-declarations](../go-declarations/SKILL.md)
- **文档化泛型 API**:在为泛型函数编写文档注释和可运行示例时,参见 [go-documentation](../go-documentation/SKILL.md)
- **命名类型参数**:在为类型参数或约束接口选择名称时,参见 [go-naming](../go-naming/SKILL.md)