2.2 类型约束深入
类型约束(type constraint)是泛型的核心 — 它定义了类型参数可以接受哪些类型。Go 1.18 引入的类型集合(type set)概念让约束表达能力大幅提升。
类型集合与 interface 约束
在泛型出现之前,interface 定义的是方法集(method set)。泛型扩展了这个概念,interface 现在也可以定义类型集(type set):
go
// 传统 interface — 方法集
type Reader interface {
Read(p []byte) (n int, err error)
}
// 泛型约束 — 类型集(也可以混合方法)
type Number interface {
~int | ~float64 // 类型集:int 或 float64(包括底层类型)
}类型集的三种形式:
| 形式 | 示例 | 含义 |
|---|---|---|
| 单一类型 | int | 恰好是 int |
| 波浪号类型 | ~int | int 和底层类型为 int 的自定义类型 |
| 联合类型 | int | float64 | int 或 float64 |
波浪号 ~ 详解
~T 表示类型 T 及其所有底层类型(underlying type)为 T 的自定义类型:
go
package main
import "fmt"
// 自定义类型
type MyInt int
type Score int
type ID int64
// ~int 接受 int、MyInt、Score(底层都是 int)
// 但不接受 ID(底层是 int64)
type IntLike interface {
~int
}
func Double[T IntLike](v T) T {
return v * 2
}
func main() {
fmt.Println(Double(10)) // 20
fmt.Println(Double(MyInt(5))) // 10
fmt.Println(Double(Score(3))) // 6
// Double(ID(7)) // 编译错误:ID 的底层类型是 int64 不是 int
}常用标准约束
Go 提供了预定义的约束,避免重复造轮子:
go
// any — 无约束(等同于 interface{})
func Print[T any](v T) { ... }
// comparable — 可以用 == 和 != 比较的类型
func Find[T comparable](slice []T, target T) int { ... }
// cmp.Ordered — 可以用 < > <= >= 比较的类型
// 包括所有整数、浮点数、字符串
import "cmp"
func Max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}自定义约束实战
go
package main
import (
"cmp"
"fmt"
)
// ========== 数字约束 ==========
// 自定整数约束
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
type Integer interface {
Signed | Unsigned
}
type Float interface {
~float32 | ~float64
}
type Number interface {
Integer | Float
}
// 使用自定义约束
func Abs[T Number](v T) T {
if v < 0 {
return -v
}
return v
}
// ========== 组合约束 ==========
// 约束可以嵌套组合
type Real interface {
Integer | Float
}
// 带方法的约束
type Stringer interface {
~string | fmt.Stringer
}
// 类型集 + 方法集混合
type ComparableStringer interface {
~string
String() string // 只接受有 String 方法的 string 类型
}
// ========== 约束的类型推断 ==========
// 约束推断
func Scale[S ~[]E, E Number](s S, factor E) S {
result := make(S, len(s))
for i, v := range s {
result[i] = v * E(factor)
}
return result
}
func main() {
fmt.Println(Abs(-42)) // 42
fmt.Println(Abs(-3.14)) // 3.14
// 约束推断 — Go 自动推断 S 和 E
nums := []int{1, 2, 3}
scaled := Scale(nums, 2)
fmt.Println(scaled) // [2 4 6]
fmt.Println(Max(3, 7)) // 7
fmt.Println(Max("abc", "xyz")) // xyz
}comparable 约束
comparable 是 Go 内置的特殊约束,只能用作类型约束,不能单独使用:
go
package main
import "fmt"
// comparable 确保可以用 == 比较
func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
// Map 的 key 必须是 comparable
type Set[T comparable] map[T]struct{}
func (s Set[T]) Add(v T) { s[v] = struct{}{} }
func (s Set[T]) Has(v T) bool { _, ok := s[v]; return ok }
func main() {
// comparable 类型:整数、浮点、字符串、指针、channel、数组
fmt.Println(Contains([]int{1, 2, 3}, 2)) // true
fmt.Println(Contains([]string{"a", "b"}, "c")) // false
// 不可比较的类型不能用 comparable 约束
// Contains([]func(){}, func(){}) // 编译错误:func 不满足 comparable
// Set 示例
s := make(Set[string])
s.Add("hello")
fmt.Println(s.Has("hello")) // true
fmt.Println(s.Has("world")) // false
}约束设计原则
go
// 1. 约束越精确越好 — 不要滥用 any
// ❌ 太宽松
func Process[T any](v T) { /* v 能做啥?几乎什么都不能 */ }
// ✅ 精确约束
func Sum[T Number](nums []T) T { /* T 支持算术运算 */ }
// 2. 优先使用标准约束
// ❌ 自己造轮子
type MyOrdered interface {
~int | ~float64 | ~string
}
// ✅ 使用标准库
import "cmp"
func Max[T cmp.Ordered](a, b T) T { ... }
// 3. 约束也是 interface,可以被嵌入
type ReadWriter interface {
Reader
Writer
}
// Reader 和 Writer 既可以用作方法集约束,也可以包含类型集
// 4. 类型集不能有重叠的类型方法
// ❌ 编译错误
type Bad interface {
~int
String() string // int 没有 String 方法
}要点总结
~T接受类型 T 及其底层类型为 T 的所有类型comparable是内置约束,接受能用==/!=比较的类型cmp.Ordered接受能用</>比较的类型- 约束可以组合:类型集
|、接口嵌套、类型集+方法集 - 约束越精确,函数能做的事情越多