Golang 泛型

Golang 泛型

Golang 泛型

泛型是一种独立于使用的特定类型编写代码的方式。使用其可以编写出适用于任意同种类型约束的函数和结构体。

Golang 的泛型特性,实际上是为 Golang 语言带来三个新的重要内容:

  1. 函数和结构体定义支持类型参数;
  2. 将 interface 定义为类型集合,也包含没有提供方法的类型;
  3. 类型推断,允许在调用函数时省略类型参数。

类型参数 在golang中类型参数的可以在函数或者自定义类型时一起声明,语法则是用方括号包围的参数列表,与普通的参数列表比较相似。

package main

import "fmt"

//在定义结构体类型时可以加入参数列表
type Message[T interface{}] struct {
	Id    string
	Value T
}

//在声明函数时也可以增加参数列表
func NewMessage[T interface{}](id string, value T) Message[T] {

	return Message[T]{
		Id:    id,
		Value: value,
	}
}

func main() {
	message := NewMessage("unique-1", 8)
	fmt.Println(message)
}

类型集合 在 1.18 版本之前,对于 interface 理解认知是:所有实现接口方法的类型都实现了该接口,即对应定义了方法集合;随着泛型特性的加入, 对于它,需要从另外一个角度理解:接口定义了一组类型,即实现这些方法的类型,即对应定义了类型集合;如下:

type MsgV interface {
	int | float32 | float64 | ~string
}

//在定义结构体类型时可以加入参数列表
type Message[T MsgV] struct {
	Id    string
	Value T
}

//在声明函数时也可以增加参数列表,函数参数类型推断,简化代码的编写
func NewMessage[T MsgV](id string, value T) Message[T] {

	return Message[T]{
		Id:    id,
		Value: value,
	}
}
  • ~string 表示底层类型为 string 的所有类型集合,即 string 类型本身,以及用它来声明的所有类型,如:type CString string
  • any 为了方便增加了空接口类型别名,如:[T interface{}] 可以用 [T any] 代替了,易于代码读写

类型推断

函数类型推断 编译器可以函数中的实参类型推断出函数类型参数的类型,这样编写的代码看起来和没有使用泛型特性一样,保持代码的简洁性。


func worker[T MsgV](id int, jobs <-chan Message[T], rs chan<- Message[T]) {

	for job := range jobs {
		fmt.Printf("%v: %v\n", job.Id, job.Value)
		time.Sleep(2 * time.Second)
		rs <- job
	}
}

func main() {

	const NUM_JOBS = 5

	jobs := make(chan Message[int])
	rs := make(chan Message[int])

	for w := 1; w <= 3; w++ {
		go worker(w, jobs, rs)
	}

	go func() {
		for i := 1; i <= NUM_JOBS; i++ {
			jobs <- NewMessage(fmt.Sprintf("%d", i), i+1) // 函数参数类型推断,简化代码的编写
		}

		close(jobs)
	}()

	for j := 1; j <= NUM_JOBS; j++ {
		fmt.Println(<-rs)
	}
}

约束类型推断 约束类型推断从类型参数约束中推到出类型参数类型,当其中一个类型参数的类型参数已知时,约束可用于推断另外一个类型参数的类型,如,S 是某类型的切片类型,E 则可以推断为该切片的元素类型。

type Point []int

func ScaleV1[E constraints.Integer](s []E, c E) []E {
	r := make([]E, len(s))

	for i, v := range s {
		r[i] = v * c
	}

	return r
}

//约束类型推断从类型参数约束中推导出类型参数的类型
func ScaleV2[S ~[]E, E constraints.Integer](s S, c E) S {
	r := make([]E, len(s))

	for i, v := range s {
		r[i] = v * c
	}

	return r
}

func main() {

	var p Point

	for j := 1; j <= 5; j++ {
		p = append(p, j)
	}

	fmt.Printf("%v\n", ScaleV1(p, 3))
	fmt.Printf("%v\n", ScaleV2(p, 4))

}

结论

  • 正确的运用泛型特性可以让编写程序更加高效;
  • 1.18 版本中的泛型特性已经基本可以符合大部分场景使用,现在是问题是当需要在生产环境下使用时,需要经历充分的测试。

参考文章:https://go.dev/blog/intro-generics

Publish on 2022-05-02,Update on 2025-02-10