首页 / Go 语言入门教程 / 类型约束

Go 语言入门教程

类型约束

本教程共 80 篇 · 第 48 篇 · 更新于 2026-07-27 · 约 8 分钟阅读

GoGo 入门教程泛型类型约束comparable底层类型union

48. 类型约束

本节目标:搞懂类型约束的几种写法,学会自定义约束、用 ~ 匹配底层类型、用 union 组合多个类型。

类型约束是什么

类型约束就是给类型参数画个圈,规定它能代表哪些类型。上一章用了 anycomparable 两个内置约束。这章讲怎么自己定义更灵活的约束。

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
}

约束本质上就是一个接口。但和普通接口不同,约束接口里可以写类型集(用 | 组合的类型),而普通接口只有方法。

Note

Go 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 inttype 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 里,又在 SignedIntint | 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+)

下一章讲泛型类型和泛型方法。