Skip to content

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
波浪号类型~intint 和底层类型为 int 的自定义类型
联合类型int | float64int 或 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 方法
}

要点总结 ​

  1. ~T 接受类型 T 及其底层类型为 T 的所有类型
  2. comparable 是内置约束,接受能用 ==/!= 比较的类型
  3. cmp.Ordered 接受能用 </> 比较的类型
  4. 约束可以组合:类型集 |、接口嵌套、类型集+方法集
  5. 约束越精确,函数能做的事情越多