类型约束
本教程共 80 篇 · 第 48 篇 · 更新于 2026-07-27 · 约 8 分钟阅读
48. 类型约束
本节目标:搞懂类型约束的几种写法,学会自定义约束、用 ~ 匹配底层类型、用 union 组合多个类型。
类型约束是什么
类型约束就是给类型参数画个圈,规定它能代表哪些类型。上一章用了 any 和 comparable 两个内置约束。这章讲怎么自己定义更灵活的约束。
union:联合类型
用 | 把多个类型组合起来,表示「可以是这些类型中的任意一个」:
func Sum[T int | int64 | float64](s []T) T {
var total T
for _, v := range s {
total += v
}
return total
}
Sum([]int{1, 2, 3}) // OK
Sum([]float64{1.1, 2.2}) // OK
Sum([]string{"a", "b"}) // 编译错误:string 不在约束里
int | int64 | float64 就是一个联合类型,表示 T 可以是这三种之一。
自定义约束
如果联合类型太长,或者想在多个地方复用,可以定义成接口:
package main
import "fmt"
// 定义一个自定义约束
type Number interface {
int | int64 | float32 | float64
}
// 使用自定义约束
func Sum[T Number](s []T) T {
var total T
for _, v := range s {
total += v
}
return total
}
func main() {
fmt.Println(Sum([]int{1, 2, 3})) // 6
fmt.Println(Sum([]float64{1.5, 2.5})) // 4
}
约束本质上就是一个接口。但和普通接口不同,约束接口里可以写类型集(用 | 组合的类型),而普通接口只有方法。
NoteGo 1.18 把接口的定义从「方法集」扩展为「类型集」。包含类型集的接口叫通用接口(General Interface),只能用作类型约束,不能当作普通接口类型使用。
~ 底层类型标记
这里有个坑。假设你定义了 type MyInt int,然后想把它传给约束为 int 的泛型函数:
type MyInt int
func Double[T int](v T) T {
return v * 2
}
func main() {
var x MyInt = 5
Double(x) // 编译错误!MyInt 不等于 int
}
虽然 MyInt 的底层类型是 int,但约束 int 只匹配 int 本身,不匹配 MyInt。
用 ~ 标记可以匹配「底层类型是某类型的所有类型」:
type Number interface {
~int | ~int64 | ~float32 | ~float64
}
func Double[T Number](v T) T {
return v * 2
}
func main() {
var x MyInt = 5
fmt.Println(Double(x)) // 10 现在可以了!
}
~int 表示「底层类型是 int 的所有类型」,包括 int 本身和 type MyInt int、type Age int 等所有以 int 为底层类型的自定义类型。
Tip写约束时,如果你希望支持自定义类型(比如
type MyString string),记得加~。不加的话只匹配确切类型。
cmp.Ordered:标准库排序约束
Go 1.21 引入了 cmp 包,里面定义了 cmp.Ordered 约束,包含所有支持排序的类型:
package main
import (
"cmp"
"fmt"
)
func Max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(3, 7)) // 7
fmt.Println(Max("apple", "banana")) // banana
fmt.Println(Max(3.14, 2.71)) // 3.14
}
cmp.Ordered 包含的类型:
~int ~int8 ~int16 ~int32 ~int64
~uint ~uint8 ~uint16 ~uint32 ~uint64 ~uintptr
~float32 ~float64
~string
Note在 Go 1.21 之前,社区用
golang.org/x/exp/constraints包。Go 1.21 把Ordered收进了标准库cmp包,推荐用标准库版本。
约束中混合方法和类型集
约束接口可以同时包含方法和类型集:
type StringerNumber interface {
~int | ~float64
String() string
}
这个约束要求:底层类型是 int 或 float64,并且实现了 String() 方法。两个条件都要满足。
type PrettyInt int
func (p PrettyInt) String() string {
return fmt.Sprintf("整数: %d", int(p))
}
func Print[T StringerNumber](v T) {
fmt.Println(v.String())
}
func main() {
Print(PrettyInt(42)) // 整数: 42
}
约束的交集和并集
并集(用 |)
type Number interface {
int | float64 | string
}
表示 T 可以是这三个类型中的任意一个。
交集(多行声明)
type SignedInt interface {
int | int32 | int64
}
type SmallNumber interface {
int | int8 | int16
SignedInt // 交集:同时满足两行
}
如果接口里有多行类型集,它们取交集。上面的 SmallNumber 要求类型既在 int | int8 | int16 里,又在 SignedInt(int | int32 | int64)里。交集只有 int。
Warning交集很容易产生空集。如果两个类型集没有交集,就是空集,任何类型都不满足,泛型函数永远无法调用。
常用约束速查
| 约束 | 含义 | 来源 |
|---|---|---|
any | 任何类型 | 内置(Go 1.18) |
comparable | 支持 == 和 != | 内置(Go 1.18) |
cmp.Ordered | 支持 < > <= >= | 标准库 cmp(Go 1.21) |
~int | ~float64 | 整数或浮点 | 自定义 |
小结
类型约束的要点:
- 用
|组合多个类型(并集) - 用
~匹配底层类型,支持自定义类型 - 约束可以定义成接口复用
- 多行类型集取交集
cmp.Ordered是最常用的排序约束(Go 1.21+)
下一章讲泛型类型和泛型方法。