0%

golang的泛型使用

Go 1.18版本开始,引入了泛型支持,接下来跟着文章的节奏一起学习下。

1. 类型形参理解

函数的 形参(parameter) 只是类似占位符的东西并没有具体的值,只有我们调用函数传入实参(argument) 之后才有具体的值。

如果我们将 形参 实参 这个概念推广一下,给变量的类型也引入和类似形参实参的概念的话,在这里我们将其称之为 类型形参(type parameter) 和 类型实参(type argument)。

1
2
3
4
// 假设 T 是类型形参,在定义函数时它的类型是不确定的,类似占位符
func Add(a T, b T) T {
return a + b
}

在上面这段伪代码中, T 被称为 类型形参(type parameter), 它不是具体的类型,在定义函数时类型并不确定。因为 T 的类型并不确定,所以我们需要像函数的形参那样,在调用函数的时候再传入具体的类型。这样我们不就能一个函数同时支持多个不同的类型了吗?在这里被传入的具体类型被称为 类型实参(type argument)。

通过引入 类型形参 和 类型实参 这两个概念,我们让一个函数获得了处理多种不同类型数据的能力,这种编程方式被称为 泛型编程。

2. 泛型

2.1 命名多了中括号的泛型类型

1
2
3
type StringSlice []string
type Float32Slie []float32
type Float64Slice []float64

那么有没有一个办法能只定义一个类型就能代表上面这所有的类型呢?

1
type Slice[T string|float32|float64 ] []T
  • T 就是上面介绍过的类型形参(Type parameter),在定义Slice类型的时候 T 代表的具体类型并不确定,类似一个占位符
  • int|float32|float64 这部分被称为类型约束(Type constraint),中间的 | 的意思是告诉编译器,类型形参 T 只可以接收 int 或 float32 或 float64 这三种类型的实参
  • 中括号里的 T int|float32|float64 这一整串因为定义了所有的类型形参(在这个例子里只有一个类型形参T),所以我们称其为 类型形参列表(type parameter list)
  • 这里新定义的类型名称叫 Slice[T]

类型定义中带 类型形参 的类型,称之为 泛型类型(Generic type)。泛型类型不能直接拿来使用,必须传入类型实参(Type argument) 将其确定为具体的类型之后才可使用。而传入类型实参确定具体类型的操作被称为 实例化(Instantiations) 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"reflect"
)

type Slice[T string | int] []T

func main() {
var a Slice[int] = []int{1, 2, 3}
fmt.Println("a type", reflect.TypeOf(a))

var b Slice[string] = []string{"Hello", "World"}
fmt.Println("b type", reflect.TypeOf(b))
}

/*
a type main.Slice[int]
b type main.Slice[string]
*/
  • map 的 demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"reflect"
)

type Map[K string | float32, V any] map[K]V // 注意这里的key不能为any,想一想为什么

func main() {
a := Map[string, int]{"a": 10, "b": 12}
//b := Map[int32, string]{} // 不能编译
c := Map[float32, float32]{10: 10}
fmt.Println("a", reflect.TypeOf(a))
fmt.Println("c", reflect.TypeOf(c))
}

/*
a main.Map[string,int]
c main.Map[float32,float32]
*/

2.2 类型嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

type WowStruct[T int | float32, S []T] struct { // 注意 S 这里是引用了 T
Data S
MaxValue T
MinValue T
}

func main() {
//a := WowStruct[int, []float32]{} // 无法编译
b := WowStruct[int, []int]{
Data: []int{1, 2, 3},
MaxValue: 1,
MinValue: 10,
}
fmt.Println("b", b)
}

/*
b {[1 2 3] 1 10}
*/

2.3 类型的套娃

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 先定义个泛型类型 Slice[T]
type Slice[T int|string|float32|float64] []T

// ✗ 错误。泛型类型Slice[T]的类型约束中不包含uint, uint8
type UintSlice[T uint|uint8] Slice[T]



// ✓ 正确。基于泛型类型Slice[T]定义了新的泛型类型 FloatSlice[T] 。FloatSlice[T]只接受float32和float64两种类型
type FloatSlice[T float32|float64] Slice[T]
// ✓ 正确。基于泛型类型Slice[T]定义的新泛型类型 IntAndStringSlice[T]
type IntAndStringSlice[T int|string] Slice[T]
// ✓ 正确 基于IntAndStringSlice[T]套娃定义出的新泛型类型
type IntSlice[T int] IntAndStringSlice[T]




// 在map中套一个泛型类型Slice[T]
type WowMap[T int|string] map[string]Slice[T]
// 在map中套Slice[T]的另一种写法
type WowMap2[T Slice[int] | Slice[string]] map[string]T

2.4 几种语法错误

  1. 定义泛型类型的时候,基础类型不能只有类型形参
1
2
type CommonType1[T int | string | float32] T  // 无法编译
type CommonType2[T int | string | float32] []T // 正确
  1. 当类型约束的一些写法会被编译器误认为是表达式时会报错。
1
2
type NewType[T *int] []T  // 错误, T *int会被编译器误认为是表达式 T乘以int,而不是int指针
type NewType2 [T (int)] []T // 错误

为了避免这种误解,解决办法就是给类型约束包上 interface{} 或加上逗号消除歧义

1
2
3
4
5
// 包上 interface
type NewType2[T interface{*int|*float64}] []T

// 如果类型约束中只有一个类型,可以添加个逗号消除歧义
type NewType3[T *int,] []T
  1. 特殊的泛型类型
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
package main

import (
"fmt"
"reflect"
)

type Wow[T int | string] int

func main() {
var a Wow[int] = 123 // 编译正确
var b Wow[string] = 123 // 编译正确
//var c Wow[string] = "hello" // 编译错误,因为"hello"不能赋值给底层类型int

fmt.Println("a", reflect.TypeOf(a))
fmt.Println("b", reflect.TypeOf(b))
fmt.Println(a + 1)
fmt.Println(b + 2)
}


/*
a main.Wow[int]
b main.Wow[string]
124
125
*/

这个例子没有什么具体意义,但是可以让我们理解泛型类型的实例化的机制。

无论传入什么类型实参,实例化后的新类型的底层类型都是 int 。所以int类型的数字123可以赋值给变量a和b,但string类型的字符串 “hello” 不能赋值给c。

2.5 类型约束的两种写法

这两种写法和实现的功能其实是差不多的,实例化之后结构体相同。但是用第一种更好,扩展性更强。

1
2
3
4
5
6
7
8
9
type WowStruct[T int|string] struct {
Name string
Data []T
}

type WowStruct2[T []int|[]string] struct {
Name string
Data T
}

3. 函数和方法

3.1 判断变量的类型

泛型类型定义的变量不能使用类型断言。

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
type Queue[T interface{}] struct {
elements []T
}

func (q *Queue[T]) Put(value T) {
value.(int) // 错误。泛型类型定义的变量不能使用类型断言

// 错误。不允许使用type switch 来判断 value 的具体类型
switch value.(type) {
case int:
// do something
case string:
// do something
default:
// do something
}

// ...
}



// 类型擦除也可以解决,用any包裹一下
switch any(value).(type) {
case int:
// do something
case string:
// do something
default:
// do something
}

虽然type switch和类型断言不能用,但我们可通过反射机制达到目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func (receiver Queue[T]) Put(value T) {
// Printf() 可输出变量value的类型(底层就是通过反射实现的)
fmt.Printf("%T", value)

// 通过反射可以动态获得变量value的类型从而分情况处理
v := reflect.ValueOf(value)

switch v.Kind() {
case reflect.Int:
// do something
case reflect.String:
// do something
}

// ...
}

你为了避免使用反射而选择了泛型,结果到头来又为了一些功能在在泛型中使用反射。请重新思考一下,自己的需求是不是真的需要用泛型?

3.2 泛型函数

1
2
3
4
5
6
7
func Add[T int | float32 | float64](a T, b T) T {
return a + b
}

Add[int](1,2) // 传入类型实参int,计算结果为 3
Add[float32](1.0, 2.0) // 传入类型实参float32, 计算结果为 3.0
Add[string]("hello", "world") // 错误。因为泛型函数Add的类型约束中并不包含string

或许你会觉得这样每次都要手动指定类型实参太不方便了。所以Go还支持类型实参的自动推导:

1
2
Add(1, 2)  // 1,2是int类型,编译请自动推导出类型实参T是int
Add(1.0, 2.0) // 1.0, 2.0 是浮点,编译请自动推导出类型实参T是float32

3.3 自定义类型的方法

定义了新的普通类型之后可以给类型添加方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
)

type MySlice[T int | string] []T

func (s MySlice[T]) Sum() T {
var sum T
for _, value := range s {
sum += value
}
return sum
}

func main() {
var s MySlice[int] = []int{1, 2, 3, 4}
fmt.Println(s.Sum()) // 输出:10

var s2 MySlice[string] = []string{"hello", "world"}
fmt.Println(s2.Sum()) // 输出:helloworld
}

3.4 泛型方法

普通结构体不支持,除非结构体自己支持泛型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type A struct {
}

// 不支持泛型方法
func (receiver A) Add[T int | float32 | float64](a T, b T) T {
return a + b
}

// 不支持泛型方法
func (receiver A[T int | float32 | float64]) Add(a T, b T) T {
return a + b
}


// 只能曲线救国方案
type A[T int | float32 | float64] struct {
}
func (receiver A[T]) Add(a T, b T) T {
return a + b
}

4. 接口

4.1 底层类型

1
2
3
4
5
6
7
8
9
10
11
12
package main

type Int interface {
int | int8 | int16 | int32 | int64
}

type Slice[T Int] []T

var s1 Slice[int] // 正确

type MyInt int
var s2 Slice[MyInt] // ✗ 错误。MyInt类型底层类型是int但并不是int类型,不符合 Slice[T] 的类型约束

为了从根本上解决这个问题,Go新增了一个符号 ~ ,在类型约束中使用类似 ~int 这种写法的话,就代表着不光是 int ,所有以 int 为底层类型的类型也都可用于实例化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

type Int interface {
~int | int8 | int16 | int32 | int64
}

type Slice[T Int] []T

var s1 Slice[int] // 正确

type MyInt int

var s2 Slice[MyInt] // 正确了

type MyInt8 int8

var s3 Slice[MyInt8] // 无法编译

限制:使用 ~ 时有一定的限制:

  1. ~后面的类型不能为接口
  2. ~后面的类型必须为基本类型

4.2 类型集(Type set)

在Go1.18之前,Go官方对 接口(interface) 的定义是:接口是一个方法集(method set)

1
2
3
4
type ReadWriter interface {
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}

ReadWriter 接口定义了一个接口(方法集),这个集合中包含了 Read() 和 Write() 这两个方法。所有同时定义了这两种方法的类型被视为实现了这一接口。

但是,我们如果换一个角度来重新思考上面这个接口的话,会发现接口的定义实际上还能这样理解:

我们可以把 ReaderWriter 接口看成代表了一个 类型的集合,所有实现了 Read() Writer() 这两个方法的类型都在接口代表的类型集合当中

1
2
3
4
5
type Float interface {
~float32 | ~float64
}

type Slice[T Float] []T

接口类型 Float 代表了一个 类型集合, 所有以 float32 或 float64 为底层类型的类型,都在这一类型集之中。

而 type Slice[T Float] []T 中, 类型约束 的真正意思是:类型约束 指定了类型形参可接受的类型集合,只有属于这个集合中的类型才能替换形参用于实例化。

1
2
3
4
5
6
7
8
9
10
11
12
package main

type Float interface {
~float32 | ~float64
}

type Slice[T Float] []T

func main() {
var s1 Slice[float32] // float32 属于类型集 float32
var s2 Slice[int] // int 类型不在类型集 Float 中,所以错误
}

接口实现(implement)定义的变化

Go1.18开始 接口实现(implement) 的定义自然也发生了变化:

当满足以下条件时,我们可以说 **类型 T 实现了接口 I ( type T implements interface I)**:

  • T 不是接口时:类型 T 是接口 I 代表的类型集中的一个成员 (T is an element of the type set of I)
  • T 是接口时: T 接口代表的类型集是 I 代表的类型集的子集(Type set of T is a subset of the type set of I)

4.3 类型并集和交集

  • 并集
1
2
3
type Uint interface {  // 类型集 Uint 是 ~uint 和 ~uint8 等类型的并集
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
  • 交集
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    type Uint interface {  // 类型集 Uint 是 ~uint 和 ~uint8 等类型的并集
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
    }
    // 接口可以不止书写一行,如果一个接口有多行类型定义,那么取它们之间的 交集
    type AllInt interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32
    }
    type Uint interface {
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
    }
    type A interface { // 接口A代表的类型集是 AllInt 和 Uint 的交集
    AllInt
    Uint
    }
    type B interface { // 接口B代表的类型集是 AllInt 和 ~int 的交集
    AllInt
    ~int
    }

上面这个例子中

接口 A 代表的是 AllInt 与 Uint 的 交集,即 ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64

接口 B 代表的则是 AllInt 和 int 的交集,即 `int`

1
2
3
4
5
6
type C interface {
~int
int
}

// 很显然,~int 和 int 的交集只有int一种类型,所以接口C代表的类型集中只有int一种类型
  • 空集
1
2
3
4
5
6
type Bad interface {
int
float32
} // 类型 int 和 float32 没有相交的类型,所以接口 Bad 代表的类型集为空

// 没有任何一种类型属于空集。虽然 Bad 这样的写法是可以编译的,但实际上并没有什么意义

4.4 空接口和 any

空接口 interface{} 。因为,Go1.18开始接口的定义发生了改变,所以 interface{} 的定义也发生了一些变更:空接口代表了所有类型的集合。

对于Go1.18之后的空接口应该这样理解:

  1. 虽然空接口内没有写入任何的类型,但它代表的是所有类型的集合,而非一个 空集

  2. 类型约束中指定 空接口 的意思是指定了一个包含所有类型的类型集,并不是类型约束限定了只能使用 空接口 来做类型形参

1
2
3
4
5
6
7
// 空接口代表所有类型的集合。写入类型约束意味着所有类型都可拿来做类型实参
type Slice[T interface{}] []T

var s1 Slice[int] // 正确
var s2 Slice[map[string]string] // 正确
var s3 Slice[chan int] // 正确
var s4 Slice[interface{}] // 正确

因为空接口是一个包含了所有类型的类型集,所以我们经常会用到它。于是,Go1.18开始提供了一个和空接口 interface{} 等价的新关键词 any ,用来使代码更简单:

1
type Slice[T any] []T // 代码等价于 type Slice[T interface{}] []T

4.5 可比较和可排序

对于一些数据类型,我们需要在类型约束中限制只接受能 !=== 对比的类型,如map:

1
2
// 错误。因为 map 中键的类型必须是可进行 != 和 == 比较的类型
type MyMap[KEY any, VALUE any] map[KEY]VALUE

所以Go直接内置了一个叫 comparable 的接口,它代表了所有可用 != 以及 == 对比的类型:(牛)

1
type MyMap[KEY comparable, VALUE any] map[KEY]VALUE // 正确

comparable 比较容易引起误解的一点是很多人容易把他与可排序搞混淆。可比较指的是 可以执行 != == 操作的类型,并没确保这个类型可以执行大小比较( >,<,<=,>= )。

而可进行大小比较的类型被称为 Orderd 。目前Go语言并没有像 comparable 这样直接内置对应的关键词。

4.6 基本接口和一般接口🔥

1
2
3
4
5
6
type ReadWriter interface {
~string | ~[]rune

Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}

接口类型 ReadWriter 代表了一个类型集合,所有以 string 或 []rune 为底层类型,并且实现了 Read() Write() 这两个方法的类型都在 ReadWriter 代表的类型集当中。这个接口要求实现者不仅要是 string[]rune 类型的变体,还需要实现 ReadWrite 两个方法。

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
40
package main

import "fmt"

type ReadWriter interface {
~string | ~[]rune

Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}

func main() {
b := BytesReadWriter("str")
b.Write([]byte("hello"))

//var i ReadWriter // 不行,因为不能定义变量
//i = b
//i.Write([]byte("world"))
}

// 类型 StringReadWriter 实现了接口 Readwriter
type StringReadWriter string

func (s StringReadWriter) Read(p []byte) (n int, err error) {
return
}
func (s StringReadWriter) Write(p []byte) (n int, err error) {
fmt.Println("111", string(p))
return
}

// 类型BytesReadWriter 没有实现接口 Readwriter, 既不是string也是不[]rune
type BytesReadWriter []byte

func (s BytesReadWriter) Read(p []byte) (n int, err error) {
return
}
func (s BytesReadWriter) Write(p []byte) (n int, err error) {
return
}

Go1.18开始将接口分为了两种类型:

1. 基本接口(Basic interface)

接口定义中如果只有方法的话,那么这种接口被称为基本接口(Basic interface)。这种接口就是Go1.18之前的接口,用法也基本和Go1.18之前保持一致。基本接口大致可以用于如下几个地方:

  • 最常用的,定义接口变量并赋值

    1
    2
    3
    4
    5
    6
    type MyError interface { // 接口中只有方法,所以是基本接口
    Error() string
    }

    // 用法和 Go1.18之前保持一致
    var err MyError = fmt.Errorf("hello world")
  • 基本接口因为也代表了一个类型集,所以也可用在类型约束中

    1
    2
    type MySlice[T io.Reader]  []Slice  // 正确
    type MySlice[T io.Reader | io.Writer] []Slice // 错误,不能用作并集

2. 一般接口(General interface)

如果接口内不光只有方法,还有类型的话,这种接口被称为 一般接口(General interface) ,如下例子都是一般接口:

1
2
3
4
5
6
7
8
9
10
type Uint interface { // 接口 Uint 中有类型,所以是一般接口
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}

type ReadWriter interface { // ReadWriter 接口既有方法也有类型,所以是一般接口
~string | ~[]rune

Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}

一般接口类型不能用来定义变量,只能用于泛型的类型约束中。所以以下的用法是错误的:

1
2
3
4
5
6
7
8
9
type Uint interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}

var uintInf Uint // 错误。Uint是一般接口,只能用于类型约束,不得用于变量定义



var i ReadWriter // error

这一限制保证了一般接口的使用被限定在了泛型之中,不会影响到Go1.18之前的代码,同时也极大减少了书写代码时的心智负担。

4.7 泛型接口

接口定义自然也可以使用类型形参,下面两个接口是泛型类型。

1
2
3
4
5
6
7
8
9
10
11
type DataProcessor[T any] interface {
Process(oriData T) (newData T)
Save(data T) error
}

type DataProcessor2[T any] interface {
int | ~struct{ Data interface{} }

Process(data T) (newData T)
Save(data T) error
}

而泛型类型要使用的话必须传入类型实参实例化才有意义。所以我们来尝试实例化一下这两个接口。因为 T 的类型约束是 any,所以可以随便挑一个类型来当实参(比如string):

1
2
3
4
5
6
7
DataProcessor[string]

// 实例化之后的接口定义相当于如下所示:
type DataProcessor[string] interface {
Process(oriData string) (newData string)
Save(data string) error
}

再用同样的方法实例化 DataProcessor2[T]

1
2
3
4
5
6
7
8
9
DataProcessor2[string]

// 实例化后的接口定义可视为
type DataProcessor2[T string] interface {
int | ~struct{ Data interface{} }

Process(data string) (newData string)
Save(data string) error
}

DataProcessor2[string] 因为带有类型并集所以它是 一般接口(General interface),所以实例化之后的这个接口代表的意思是:

  1. 只有实现了 Process(string) stringSave(string) error 这两个方法,并且以 intstruct{ Data interface{} } 为底层类型的类型才算实现了这个接口
  2. 一般接口(General interface) 不能用于变量定义只能用于类型约束,所以接口 DataProcessor2[string] 只是定义了一个用于类型约束的类型集
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
// XMLProcessor 虽然实现了接口 DataProcessor2[string] 的两个方法,但是因为它的底层类型是 []byte,所以依旧是未实现 DataProcessor2[string]
type XMLProcessor []byte
func (c XMLProcessor) Process(oriData string) (newData string) {
}
func (c XMLProcessor) Save(oriData string) error {

}

// JsonProcessor 实现了接口 DataProcessor2[string] 的两个方法,同时底层类型是 struct{ Data interface{} }。所以实现了接口 DataProcessor2[string]
type JsonProcessor struct {
Data interface{}
}
func (c JsonProcessor) Process(oriData string) (newData string) {
return
}
func (c JsonProcessor) Save(oriData string) error {
return nil
}



// 错误。DataProcessor2[string]是一般接口不能用于创建变量
var processor DataProcessor2[string]

// 正确,实例化之后的 DataProcessor2[string] 可用于泛型的类型约束
type ProcessorList[T DataProcessor2[string]] []T

// 正确,接口可以并入其他接口
type StringProcessor interface {
DataProcessor2[string]
PrintString()
}

// 错误,带方法的一般接口不能作为类型并集的成员
type StringProcessor interface {
DataProcessor2[string] | DataProcessor2[[]byte]
PrintString()
}

4.8 接口定义的种种限制规则

  1. | 连接多个类型的时候,类型之间不能有相交的部分(即必须是不交集):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    type MyInt int

    // 错误,MyInt的底层类型是int,和 ~int 有相交的部分
    type _ interface {
    ~int | MyInt
    }


    // 但是相交的类型中是接口的话,则不受这一限制:
    type MyInt int

    type _ interface {
    ~int | interface{ MyInt } // 正确
    }

    type _ interface {
    interface{ ~int } | MyInt // 也正确
    }

    type _ interface {
    interface{ ~int } | interface{ MyInt } // 也正确
    }
  2. 类型的并集中不能有类型形参

    1
    2
    3
    4
    5
    6
    7
    type MyInf[T ~int | ~string] interface {
    ~float32 | T // 错误。T是类型形参
    }

    type MyInf2[T ~int | ~string] interface {
    T // 错误
    }
  3. 接口不能直接或间接地并入自己

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    type Bad interface {
    Bad // 错误,接口不能直接并入自己
    }

    type Bad2 interface {
    Bad1
    }
    type Bad1 interface {
    Bad2 // 错误,接口Bad1通过Bad2间接并入了自己
    }

    type Bad3 interface {
    ~int | ~string | Bad3 // 错误,通过类型的并集并入了自己
    }

  4. 接口的并集成员个数大于一的时候不能直接或间接并入 comparable 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    type OK interface {
    comparable // 正确。只有一个类型的时候可以使用 comparable
    }

    type Bad1 interface {
    []int | comparable // 错误,类型并集不能直接并入 comparable 接口
    }

    type CmpInf interface {
    comparable
    }
    type Bad2 interface {
    chan int | CmpInf // 错误,类型并集通过 CmpInf 间接并入了comparable
    }
    type Bad3 interface {
    chan int | interface{comparable} // 理所当然,这样也是不行的
    }
  5. 带方法的接口(无论是基本接口还是一般接口),都不能写入接口的并集中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    type _ interface {
    ~int | ~string | error // 错误,error是带方法的接口(一般接口) 不能写入并集中
    }

    type DataProcessor[T any] interface {
    ~string | ~[]byte

    Process(data T) (newData T)
    Save(data T) error
    }

    // 错误,实例化之后的 DataProcessor[string] 是带方法的一般接口,不能写入类型并集
    type _ interface {
    ~int | ~string | DataProcessor[string]
    }

    type Bad[T any] interface {
    ~int | ~string | DataProcessor[T] // 也不行
    }

5. 参考资料

可以加首页作者微信,咨询相关问题!