wavelet init

This commit is contained in:
ryan
2026-06-18 15:24:48 +08:00
parent d6a7011885
commit 99738bbc17
714 changed files with 139987 additions and 0 deletions
+173
View File
@@ -0,0 +1,173 @@
---
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)
@@ -0,0 +1,169 @@
# Go 泛型中的类型约束
> **来源**:Google Go 风格指南、Go 语言规范
约束定义了类型参数支持的操作。选择满足函数需求的最窄约束——不要更多。
---
## 内置约束
> **规范**:在自行编写约束之前,优先使用标准约束。
| 约束 | 含义 |
|------|------|
| `any` | `interface{}` 的别名;对类型没有要求 |
| `comparable` | 支持 `==` 和 `!=`;映射键所必需 |
| `cmp.Ordered` | 支持 `<`、`<=`、`>=`、`>`(Go 1.21+,替代 `constraints.Ordered`) |
在新代码中优先使用 `cmp.Ordered`(来自 `cmp` 包),而不是已弃用的 `golang.org/x/exp/constraints.Ordered`。
---
## `~` 运算符(底层类型)
> **建议**:当你想接受基于原始类型构建的命名类型时使用 `~`。
`~T` 语法匹配任何**底层类型**为 `T` 的类型。没有 `~` 时,只有精确的类型匹配。
```go
type Celsius float64
type ExactFloat interface{ float64 } // 拒绝 Celsius
type AnyFloat64 interface{ ~float64 } // 接受 Celsius
```
当调用者可能基于基础类型定义命名类型时使用 `~`。仅在需要限制为精确的内置类型时才省略 `~`。
---
## 组合与编写约束
> **建议**:仅在没有标准约束适用时才定义自定义约束。
使用 `|` 组合类型并嵌入约束来组合它们:
```go
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~float32 | ~float64
}
type Addable interface {
Numeric | ~string // 数字和字符串拼接
}
```
约束可以同时要求方法和类型元素:
```go
type Stringer interface {
comparable
String() string
}
```
满足 `Stringer` 的类型必须是可比较的 **并且** 具有 `String()` 方法。
---
## 避免过度约束
> **规范**:使用支持所执行操作的最小约束。
**不好**
```go
// 只使用了 == 但限制为 int 和 string
func Contains[T interface{ ~int | ~string }](s []T, v T) bool { ... }
```
**好**
```go
// comparable 是 == 的最小约束
func Contains[T comparable](s []T, v T) bool { ... }
```
过度约束限制了复用,并迫使调用者绕过实现中根本不需要的限制。
## 类型推断
> **建议**:当类型明确时让编译器推断类型参数。
编译器从函数参数推断类型参数:
```go
result := slices.Contains[string](names, "alice") // 显式——不必要
result := slices.Contains(names, "alice") // 推断——推荐
```
仅在以下情况下才显式提供类型参数:没有可用于推断的函数参数、推断的类型不正确(例如无类型常量提升为错误的类型),或者将类型显式展示出来有助于可读性。
---
## 常见陷阱
### 接口已足够时不要使用泛型
> **规范**:来自 Google 风格指南——当类型共享一个有用的统一接口时,优先使用接口。
**不好**
```go
// T 仅用于满足 io.Reader——直接使用接口即可
func Process[T io.Reader](r T) error { ... }
```
**好**
```go
func Process(r io.Reader) error { ... }
```
如果约束是单个已有接口,直接接受该接口。
### 不要泛型地包装标准库类型
> **建议**:单次使用的泛型只是多余的间接层。
**不好**
```go
type Set[T comparable] struct{ m map[T]struct{} } // 永远只是 Set[string]
```
**好**
```go
seen := map[string]struct{}{} // 对于单次实例化直接使用 map
```
泛型在消除**多个调用点**之间的重复时才能证明其复杂度的合理性。如果只使用一种类型,从具体类型开始。
### 方法集与类型约束
你只能调用约束允许的操作:
**不好**
```go
func Stringify[T any](v T) string {
return v.String() // 编译错误:any 没有 String()
}
```
**好**
```go
func Stringify[T fmt.Stringer](v T) string {
return v.String()
}
```
---
## 快速参考
| 主题 | 指导 |
|------|------|
| 默认约束 | `any`——不需要对 T 进行任何操作时使用 |
| 相等性检查 | `comparable`——`==`、`!=` 和映射键所必需 |
| 排序 | `cmp.Ordered`(Go 1.21+)用于 `<`、`>` 比较 |
| 命名类型 | 使用 `~T` 接受底层类型为 T 的类型 |
| 联合类型 | 使用 `\|` 组合——例如 `~int \| ~float64` |
| 自定义约束 | 定义为包含类型元素和/或方法的接口 |
| 类型推断 | 当编译器可以推断时省略类型参数 |
| 最小约束 | 使用函数实际需要的最窄约束 |