限制

  1. 对泛型函数的类型参数以及泛型函数中的实现代码设置限制
    • 泛型函数的调用者只能传递满足限制条件的类型实参
    • 泛型函数内部也只能以类型参数允许的方式使用这些类型实参值
  2. 在 Go 中,使用类型参数约束来表达这种限制条件
    • 函数普通参数在函数实现中可以表现出来的性质与可以参与的运算由参数类型限制
    • 泛型函数的类型参数约束来限制

image-20231209103602436

内置约束

any

最宽松的约束

  1. 无论是泛型函数还是泛型类型,其所有的类型参数声明中都必须显式包含约束
  2. 可以使用空接口类型来表达所有类型
1
2
3
func foo[T interface{}](sl []T) {}

func bar[T1 interface{}, T2 interface{}](t1 T1, t2 T2) {}

空接口类型的不足:使得声明变得冗长复杂语义不清

builtin/builtin.go
1
2
// any is an alias for interface{} and is equivalent to interface{} in all ways.
type any = interface{}

any 约束的类型参数意味着可以接受所有类型作为类型实参,而在函数体内,可以执行

  1. 声明变量
  2. 同类型赋值
  3. 将变量传给其它函数或者从函数返回
  4. 取变量地址
  5. 转换或者赋值给 interface{} 类型变量
  6. 用在 type switch 或者 type assertion 中
  7. 作为复合类型中的元素类型
  8. 传递给预定义的函数,如 new
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func foo[T1, T2 any](t1 T1, t2 T2) T1 {
var a T1 // 声明变量
var b T2

a, b = t1, t2 // 同类型赋值
_, _ = a, b

f := func(t T1) T1 { return t }
a = f(a) // 将变量传给其它函数并从函数返回

p := &a // 取变量地址
_ = p

var i interface{} = a // 转换或者赋值给 interface{} 类型变量
_ = i

sl := make([]T1, 0, 10) // 作为复合类型中元素类型
sl = append(sl, a)

j, ok := i.(T1) // type assertion
_, _ = j, ok

switch i.(type) { // type switch
case T1:
_ = i.(T1) // type assertion
case T2:
_ = i.(T2) // type assertion
}

c := new(T1) // 传递给预定义函数
_ = c

return a // 从函数返回
}

any 约束不支持比较

1
2
3
4
5
6
7
8
func foo[T1, T2 any](t1 T1, t2 T2) T1 {
var a T1
if a == t1 { // invalid operation: a == t1 (incomparable types in type set)
}
if a != t1 { // invalid operation: a != t1 (incomparable types in type set)
}
return a
}

comparable

编译器会在编译期间判断某个类型是否实现了 comparable 接口

builtin/builtin.go
1
2
3
4
5
6
// comparable is an interface that is implemented by all comparable types
// (booleans, numbers, strings, pointers, channels, arrays of comparable types,
// structs whose fields are all comparable types).
// The comparable interface may only be used as a type parameter constraint,
// not as the type of a variable.
type comparable interface{ comparable }

booleans, numbers, strings, pointers, channels,
arrays of comparable types, structs whose fields are all comparable types

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
type foo struct {
a int
s string
}

type bar struct {
a int
sl []string
}

func compare[T comparable](t T) T {
var a T
if a == t {
}
if a != t {
}
return a
}

func main() {
compare(true)
compare(3)
compare(3.14)
compare(3 + 4i)
compare("go")
var p *int
compare(p)
compare(make(chan int))
compare([3]int{1, 2, 3})
compare([]int{1, 2, 3}) // []int does not implement comparable
compare(foo{})
compare(bar{}) // bar does not implement comparable
}

not as the type of a variable

1
var i comparable = 5 // cannot use type comparable outside a type constraint: interface is (or embeds) comparable

自定义约束

Go 泛型使用 interface 语法来定义约束,凡是接口类型均可作为类型参数约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func Stringify[T fmt.Stringer](s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String())
}
return
}

type MyString string

func (s MyString) String() string {
return string(s)
}

func main() {
sl := Stringify([]MyString{"Hello", "World"})
fmt.Println(sl) // [Hello World]
}
fmt/print.go
1
2
3
type Stringer interface {
String() string
}
  1. 类型参数 T 的实参必须实现 fmt.Stringer 接口的所有方法
  2. 泛型函数的实现代码中,声明的 T 类型实例 v 仅被允许调用 fmt.Stringer 的 String 方法

只处理非零值的元素,编译报错

1
2
3
4
5
6
7
8
9
10
func Stringify[T fmt.Stringer](s []T) (ret []string) {
var zero T // zero value of T
for _, v := range s {
if v == zero { // invalid operation: v == zero (incomparable types in type set)
continue
}
ret = append(ret, v.String())
}
return
}

通过 type embedding 扩充约束语义,内嵌 comparable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

type Stringer interface {
comparable // type embedding
fmt.Stringer // type embedding
}

func Stringify[T Stringer](s []T) (ret []string) {
var zero T // zero value of T
for _, v := range s {
if v == zero { // ok
continue
}
ret = append(ret, v.String())
}
return
}

type MyString string

func (s MyString) String() string {
return string(s)
}

func main() {
sl := Stringify([]MyString{"Hello", "", "World", ""})
fmt.Println(sl) // [Hello World]
}

增加对排序的支持,编译报错,Go 不支持运算符重载

1
2
3
4
5
6
7
8
9
10
func Stringify[T Stringer](s []T, max T) (ret []string) {
var zero T // zero value of T
for _, v := range s {
if v == zero || v >= max { // invalid operation: v >= max (type parameter T is not comparable with >=)
continue
}
ret = append(ret, v.String())
}
return
}

Go 支持在接口类型中放入类型元素(type element)信息,之前只有方法元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import "fmt"

type ordered interface {
// 以列表中的类型为底层类型的类型都满足 ordered 约束
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string
}

type Stringer interface {
ordered // 包含类型元素,仅能被作为约束,内嵌到其它接口
comparable // type embedding
fmt.Stringer // type embedding
}

func Stringify[T Stringer](s []T, max T) (ret []string) {
var zero T // zero value of T
for _, v := range s {
if v == zero || v >= max { // ok
continue
}
ret = append(ret, v.String())
}
return
}

type MyString string

func (s MyString) String() string {
return string(s)
}

func main() {
sl := Stringify([]MyString{"Hello", "", "World", ""}, MyString("World"))
fmt.Println(sl) // [Hello]
}

约束定义

type 仅代表自身;~type 代表以该类型为底层类型的所有类型

image-20231209143945841

1
2
3
4
5
6
7
type Ia interface {
int | string // 仅代表 int 和 string
}

type Ib interface {
~int | ~string // 代表以 int 和 string 为底层类型的类型
}

image-20231209144616103

union elements不能包含带有方法元素的接口类型,也不能包含预定义约束类型,如 comparable

1
2
3
4
5
6
7
8
9
10
11
type A interface {
}

type B interface {
A | comparable // cannot use comparable in union
M2()
}

type C interface {
A | B // cannot use main.B in union (main.B contains methods)
}

接口分类:一旦包含了类型元素,只能用于约束

  1. 基本接口类型 - 类型元素
    • 自身和其嵌入的接口类型都只包含方法元素,而不包含类型元素
    • 可以当成常规接口类型使用,也可以作为泛型类型参数约束
  2. 非基本接口类型 - 类型元素
    • 直接或者间接(type embedding)包含了类型元素的接口类型
    • 仅可用作泛型类型参数约束,或者被嵌入到其它仅作为约束的接口类型中
类型元素 用途
常规接口 + 约束
约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type BasicInterface interface { // 基本接口类型
M1()
}

type NonBasicInterface interface { // 非基本接口类型
BasicInterface
~int | ~string // 包含类型元素
}

type MyString string

func (MyString) M1() {}

func foo[T BasicInterface](t T) {} // 基本接口类型作为约束

func bar[T NonBasicInterface](t T) {} // 非基本接口类型作为约束

func main() {
var s = MyString("hello")

var bi BasicInterface = s // ok,基本接口类型支持常规用法
bi.M1()
foo(s)

var nbi NonBasicInterface = s // cannot use type NonBasicInterface outside a type constraint: interface contains type constraints
nbi.M1()
bar(s)
}
  1. 基本接口类型仅包含方法元素
    • 可以基于方法集合来确定一个类型是否实现了接口
    • 判断是否可以作为类型实参传递给约束下的类型形参
  2. 非基本接口类型,既有方法元素,也有类型元素
    • 借助类型集合(type set)来判断类型实参是否满足约束

类型集合只是重新解释类型实现接口的判断规则

类型集合并非一个运行时概念,因此无法通过运行时反射来查看某个接口类型的类型集合

类型集合

接口类型的类型集合中的元素可以满足以该接口类型作为类型约束
可以将集合中的元素作为类型实参传递给该接口类型约束类型参数

  1. 每个类型都有一个类型集合
  2. 非接口类型的类型集合仅包含其自身
  3. 用于定义约束接口类型的类型集合
    • 空接口类型的类型集合是一个无限集合,该集合中的元素为所有的非接口类型
    • 非空接口类型的类型集合为其定义中的接口元素类型集合交集

image-20231210110217812

接口元素:其它嵌入接口类型、方法元素、类型元素

接口元素 类型集合
其它嵌入接口类型 该嵌入接口类型的类型集合
方法元素 该方法的类型集合
类型元素 该类型元素所表示的所有类型组成的集合
如果为 ~T 形式,该集合中还包含所有以 T 为底层类型的类型

一个方法的类型集合为实现了该方法的非接口类型的集合,也是一个无限集合
包含多个方法的常规接口类型的类型集合,为这些方法元素的类型集合的交集

image-20231210111517445

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Intf1 interface {
~int | string
F1()
F2()
}

type Intf2 interface {
~int | ~float64
}

type I interface {
Intf1
M1()
M2()
int | ~string | Intf2
}

接口类型 I 由 4 个接口元素组成:Intf1、M1、M2、Union element(int | ~string | Intf2)

  1. Intf1
    • Intf1 是接口类型 I 的一个嵌入接口,其自身由 3 个接口元素组成,其类型集合为这 3 个接口元素的交集
    • Intf1 的类型集合
      • {以 int 为底层类型的所有类型或者 string 类型,并且实现了 F1 和 F2 方法的所有类型}
      • 由于不可能为 string 新增方法 F1 和 F2
      • {以 int 为底层类型并且实现了 F1 和 F2 方法的所有类型}
  2. M1 / M2
    • 方法的类型集合由所有实现该方法的类型组成
    • M1 的类型集合:{实现了 M1 的所有类型}
    • M2 的类型集合:{实现了 M2 的所有类型}
  3. int | ~string | Intf2
    • 类型集合为 int、~string、Intf2 类型集合的并集
      • int 的类型集合:int
      • ~string 的类型集合:以 string 为底层类型的所有类型
      • Intf2 的类型集合:以 int 或者 float64 为底层类型的所有类型
    • int | ~string | Intf2 的类型集合:{以 int、string、float64 为底层类型的所有类型或者 int 类型}

I 的类型集合:{以 int 为底层类型,并且实现了 F1、F2、M1、M2 的所有类型}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func foo[T I](t T) {}

type MyInt int

func (MyInt) F1() {}

func (MyInt) F2() {}

func (MyInt) M1() {}

func (MyInt) M2() {}

func main() {
var a int = 11
foo(a) // int does not implement I (missing F1 method)
foo(MyInt(11)) // ok
}

语法糖

1
2
3
4
5
6
type I interface { // 独立于泛型函数外面定义
~int | ~string
}

func foo[T I](t T) {}
func bar[T interface{ ~int | ~string }](t T) {} // 以接口类型字面量作为约束

如果约束对应的接口类型中仅有一个接口元素,且该元素为类型元素时,可进一步简化

1
func bar[T ~int | ~string](t T) {}
1
2
3
4
5
func bar[T interface{T1 | T2 ... | Tn}](t T)

// 等价于

func bar[T T1 | T2 ... | Tn](t T)

定义仅包含一个类型参数泛型类型时,如果约束仅有一个 *int 类型元素,会报错

1
2
3
4
5
// undefined: T
// int (type) is not an expression
type MyStruct [T * int]struct{}

// 编译器会理解为一个类型声明:MyStruct 为新类型的名字,底层类型为 [T * int]struct{}

解决方案

1
2
3
4
5
// 完整形式
type MyStruct[T interface{ *int }] struct{}

// 加一个逗号
type MyStruct[T *int,] struct{}

类型推断

1
func DoubleDefined[S ~[]E, E ~int | ~string](s S) S { return s }
  1. 在大多数情况下,可以根据函数实参推断出类型实参
  2. 光靠函数实参的推断,是无法完全推断出 DoubleDefined 的所有类型实参
    • DoubleDefined 的类型参数 E 并未在常规参数列表中用来声明参数
    • 函数实参推断仅能推断出类型参数 S 的类型实参,而无法推断出 E 的类型实参
  3. Go 支持约束类型推断,即基于一个已知的类型实参来推断其它类型参数的类型

函数实参 -> 类型实参 S -> 类型实参 E