今天继续学习Go 1.18引入的泛型,每天利用几分钟的时间来学习,慢慢积累。

前两天学习了在Go中如何定义泛型函数,并学习了Go泛型中的类型约束(Type Constrant)的概念,今天通过参考Go官方的泛型技术提案文档,简单整理一下Go泛型的基本语法。

Go的泛型指的是在Go的类型声明和函数声明中增加可选的类型参数。类型参数要受到类型约束,Go使用嵌入额外元素的接口类型来定义类型约束,类型约束定义了一组满足约束的类型集(Type Set)。

类型参数(Type Parameter)

Go泛型代码可以在Go的函数声明或类型声明中增加类型参数列表,而在类型参数列表中声明的类型名称就是类型参数。

类型参数在声明中充当了一个未知类型的占位符,在泛型函数或泛型类型被具化(instantiation)时,类型参数将会被"类型实参"所替换。

在函数声明中,可以在函数名称的后边,使用中括号声明一个额外的类型参数列表,例如 func F[T any](p T) { ... }。声明的类型参数可以在函数的参数和函数体中使用。 在这个例子中,T是类型参数的名字,也就是类型,any是类型参数的约束,是对类型参数可选类型的约束。但是T的类型要等到泛型函数具化时才能确定。

在类型声明中,可以在类型名称的后边,使用中括号声明一个额外的类型参数列表,例如:

1
2
3
4
5
type M[T any] []T

type Stack[T any] struct {
	eles []T
}

类型约束(Type Constraint)

每个类型参数都有一个类型约束,类型约束规定了类型参数在具化为类型实参时必须满足的条件。

Go的泛型中使用接口类型来定义类型约束。

Go 1.18内置了anycomparable两个类型约束。any表示任意类型,comparable表示类型的值可以使用==!=比较大小。 在$GOROOT/src/builtin/builtin.go中可找到它们的源码,any实际上就是interface{}的别名。

1
2
3
4
5
6
7
8
9
// any is an alias for interface{} and is equivalent to interface{} in all ways.
type any = interface{}

// 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 }

为了支持使用接口类型来定义Go泛型类型参数的类型约束,Go 1.18对接口定义语法进行了扩展。 在接口定义中既可以定义接口的方法集(Method Set),也可以声明可以被用作泛型类型参数的类型实参的类型集(Type Set)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type Constraint1 interface {
	T1 // 约束限制为T1类型
}

type Constraint2 interface {
	~T1 // 约束限制为所有底层类型为T1的类型
}

type Constraint3 interface {
	T1 | T2 | T3 // 约束限制为T1, T2, T3中的一个类型
}

注意,虽然在接口中既可以定义Method Set,也可以定义Type Set,但在定义接口时还是建议将传统的接口定义需求和用作泛型类型参数约束的需求区分开,推荐分开定义,而不要混用。

类型具化(Type Instantiation)

声明了泛型函数、泛型类型后,就可以在具体的代码中调用泛型函数和使用泛型类型。

例如,下面代码中在Go中实现了泛型的栈。

例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
32
33
34
35
36
37
package main

type Stack[T any] struct {
	eles []T
}

func NewStack[T any]() *Stack[T] {
	return &Stack[T]{
		eles: []T{},
	}
}

func (s *Stack[T]) IsEmpty() bool {
	return len(s.eles) == 0
}

func (s *Stack[T]) Push(ele T) {
	s.eles = append(s.eles, ele)
}

func (s *Stack[T]) Pop() (T, bool) {
	size := len(s.eles)
	if size == 0 {
		var zero T
		return zero, false
	}
	top := s.eles[size-1]
	s.eles = s.eles[:size-1]
	return top, true
}

func main() {
	st := NewStack[string]()
	st.Push("a")
	st.Push("b")
	st.Push("c")
}

例1中定义了泛型类型结构体Stack[T any], T是这个泛型类型的类型参数,any是类型参数T的类型约束。

例1还定义了一个泛型函数func NewStack[T any]() *Stack[T], T是这个泛型函数的类型参数,any是类型参数T的类型约束。

在main函数中st := NewStack[string]()创建了一个Stack,就是类型的具化,泛型函数的类型参数T被"类型实参"string所替换。

例1的第24行var zero T演示了如何在泛型代码中使用泛型类型的零值。

参考