快速开始
官方文档: go.dev/doc/tutoria... , Go的泛型是1.18版本开始支持的,所以使用的话最低版本要求是1.18
一、快速开始
写法的话可以看下面这个例子,代码有点长,主要是看看长啥样子就行了,现在编辑器很智能,写错了也会帮你改,不行问问GPT!
go
package main
import (
"fmt"
"reflect"
)
// Ptr 泛型函数
func Ptr[T any](input T) *T {
return &input
}
func FromPtr[T any](input *T) (_ T) {
if input == nil {
return
}
return *input
}
// KV 泛型结构体
type KV[K comparable, V any] struct {
Key K
Value V
}
func (k KV[K, V]) String() string {
// 反射API是没啥变更的
f0 := reflect.TypeOf(k).Field(0)
f1 := reflect.TypeOf(k).Field(1)
return fmt.Sprintf("KV[%s, %s]{Key: %v, Value: %v}", f0.Type, f1.Type, k.Key, k.Value)
}
func (kv *KV[K, V]) SetValue(value V) {
kv.Value = value
}
// IndexFunc 泛型参数是不区分先后定义顺序的,即不需要把类型参数E申明到前面
func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
for index, elem := range s {
if f(elem) {
return index
}
}
return -1
}
func MapFromSlice[Input, Output any](inputs []Input, handle func(Input) Output) []Output {
r := make([]Output, 0, len(inputs))
for _, elem := range inputs {
r = append(r, handle(elem))
}
return r
}
// Number 类型约束
type Number interface {
~int | ~float64 | ~int64
}
type IntNumber int
func ToString[T Number](t T) string {
return fmt.Sprintf("%v", t)
}
func NewStringKV[V any]() KV[string, V] {
return KV[string, V]{}
}
// Min 支持可变参数
func Min[T Number](x T, y ...T) T {
min := x
for _, elem := range y {
if elem < min {
min = elem
}
}
return min
}
func main() {
fmt.Println(Min(2, 1, 3))
numbers := []int{0, 42, -10, 8}
fmt.Println(IndexFunc(numbers, func(n int) bool {
return n < 0
}))
// 支持参数类型推导
var intp *int
fmt.Println(FromPtr(intp))
fmt.Println(*Ptr(1))
// 类型约束, 也就是说 ~ 符号类型推导,是不会丢失原始类型的!
fmt.Println(ToString(1)) // ToString[int](int)
fmt.Println(ToString(IntNumber(1))) // 注意这里实际上是 ToString[IntNumber](IntNumber)
fmt.Println(MapFromSlice([]KV[string, int]{{Key: "1", Value: 1}, {Key: "2", Value: 2}}, func(input KV[string, int]) string {
return input.Key
}))
fmt.Println(KV[string, int]{Key: "1", Value: 1})
fmt.Println(KV[string, string]{Key: "1", Value: "1"})
// 支持type=interface{}/any等任何类型
kv := NewStringKV[interface{}]()
kv.Value = "1"
fmt.Println(kv.Value.(string))
}
二、兼容性问题上,目前和旧的类型系统还是保持兼容的
go
package main
import "net/http"
type Cache[V any] interface {
Get(key string) V
Set(key string, v V)
}
func main() {
var cache Cache[string] = http.Header{} // 兼容普通类型
cache.Get("1")
var cache2 Cache[interface{}] // 支持interface{}为泛型参数类型
cache2.Get("1")
}
三、目前GO1.21已经正式发布了,我们可以看一下1.18-1.21带来的新特性
- 由于和Go1.18发布仅间隔5个月未引入大量的特性
- 优化了泛型的编译速度,大概提升15%左右
- 优化了
comparable
类型的推断逻辑
go
package main
func doSth[T comparable](t T) T {
return t
}
func main() {
n := 2
var i interface{} = n
doSth(i) // 在go1.18编译不通
}
- 引入了 maps / slices / cmp 库,都是是一些泛型工具库,方便使用!
- 引入了
min
/max
/clear
内置函数 - 更智能的类型推导,下面这个代码需要在Go1.21中才能编译通过 godbolt.org/z/vEPW8T6Kr
go
package main
import (
"fmt"
)
type Ordered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string
}
// Min 支持可变参数
func Min[T Ordered](x T, y ...T) T {
min := x
for index, elem := range y {
if elem < min {
min = elem
}
}
return min
}
func main() {
fmt.Println(Min(1, 1.1, 1.2, 1.3, -1, -1.1)) // 在Go1.21中才能编译通过,其他版本会报错 default type float64 of 1.2 does not match inferred type int for T, Go低版本的做法是把它推导成了 Min[int](...),1.21是Min(float64)
fmt.Println(min(1, 1, 1.2, 1.3, -1, -1.1))
}
底层实现
一、底层实现
底层实现和C++差不多,都是要进行泛型(模版)函数/类型的实例化的,也就是说会为不同类型生成不同的函数/类型信息,所以会就变相的导致二进制的庞大,但是带来的好处就是性能没有劣化!
具体我们可以通过查看Go的汇编代码可以发现: godbolt.org/z/8M7qeTxYn ,实际上调用泛型函数的时候调用的是不同的函数名。
其次Go的实现区别于Java语言,Java会在生成字节码的泛型会被擦除掉,具体可以看 godbolt.org/z/bhGKhdcco ,但实际上在runtime阶段是可以拿到泛型信息的!
二、可能存在的一些坑
GO1.18版本之前大家喜欢用 swith type
来进行类型判断,这里实际上在泛型中是行不通的!
go
package main
func ToString[T int64 | float64](num T) string {
switch num.(type) { // 这里编译失败,原因是实例化后的代码,num的类型是int64/float64而不是interface{}
case int64:
case float64:
}
return ""
}
三、如何编译Go输出汇编
shell
go build -v -o main -gcflags="-N -l" main.go
# -gnu 会携带输出at&t,方便查看
go tool objdump -gnu -S main > main.s
# 等价于上面图片的输出,坏处就是输出的是plan9汇编格式
go tool compile -N -l -S main.go
一些缺陷
没函数重载
通常我们需要用到泛型做数据类型转换,但是对于一些复杂类型的参数可能会出现以下的代码,因为Go不支持函数重载最终导致还是会出现case by case 的定义一些函数,例如下面函数是一个 map 函数,可以实现a->b 集合的转换.
go
package utils
func MapFromSlice[Input, Output any](input []Input, handler func(Input) Output) []Output {
ret := make([]Output, 0, len(input))
for _, elem := range input {
ret = append(ret, handler(elem))
}
return ret
}
func MapFromMap[IK comparable, IV any, Output any](input map[IK]IV, handler func(key IK, value IV) Output) []Output {
ret := make([]Output, 0, len(input))
for k, v := range input {
ret = append(ret, handler(k, v))
}
return ret
}
说实话Go有妥协,例如 go 在 1.21 中引入的 clear
函数就是妥协,但是目前没有把这个妥协的能力开放给开发者
go
package main
// 编译条件: go1.21
// func clear[T ~[]Type | ~map[Type]Type1](t T)
func main() {
arr := []int{3, 2, 4, 6}
clear(arr) // 最终会调用 runtime.memclrNoHeapPointers的函数,实现整个数组元素都清零
_map := map[string]int{"1": 1}
clear(_map) // 最终会调用runtime.mapclear的函数,清空整个map
}
然而如果支持了函数重载,那么开发者也可以很轻松的实现相似的代码!
类型自动推导的坑
上面讲了go1.21引入了更加智能的类型推导,但是实际上他会造成业务逻辑的BUG,如下面例子,这个就很难避免了需要个人去避免了!!
go
package main
import (
"fmt"
"math"
)
// func min[T cmp.Ordered](x T, y ...T) T
func main() {
fmt.Println(min(math.MaxInt64, math.MaxFloat64)) // 推导成了 min[float64](float64, float64)float64 导致输出 int64->float64自动转型 // 9.223372036854776e+18
fmt.Println(math.MaxInt64) // 9223372036854775807
fmt.Println(float64(math.MaxInt64)) // 9.223372036854776e+18
}
不支持类型特化
泛型中大量使用了 any/comparable 这种 generic 类型,就会导致一个问题,这种类型范围太大了,如果我们想针对一种类型进行优化的时候就显得很麻烦了!!因此特化就显得非常重要!
go
package main
import "fmt"
func ToString[T any](input T) string {
return fmt.Sprintf("%v", input)
}
func ToString[T fmt.Stringer](input T) string { //编译失败
return input.String()
}
func ToString[T ~string](input T) string { //编译失败
return string(input)
}
C++中是支持特化的,其次也支持部分参数特化和全特化!下面代码不是通过特化实现的,主要是类型推导的原因,采用的 SFINAE + 重载实现的,但是最终也实现了特化的效果(C++的模版编程特别复杂,所以现代语言基本没有参考C++的做法)!代码示例: godbolt.org/z/dz37T8xYj
cpp
#include <iostream>
#include <string>
template <typename T>
concept is_std_to_string_v = requires(T t) {
{ std::to_string(t) } -> std::same_as<std::string>;
};
template <typename T>
requires is_std_to_string_v<T>
inline std::string to_string(T t) { // f1
return std::to_string(t);
}
template <typename T>
requires std::is_convertible_v<T, std::string>
inline std::string to_string(T t) { // f2
return t;
}
inline std::string to_string(bool b) { // f3
return b ? "true" : "false";
}
int main() {
std::cout << to_string(1) << std::endl; // 调用的f1
std::cout << to_string("1") << std::endl; // 调用的f2
std::cout << to_string(true) << std::endl; // 调用的f3
}
不支持lambda(类型简化)
上面讲到的MapFromSlice 和 MapFromMap 函数我们发现
go
type KV[K, V any] struct {
Key K
Value V
}
type Item[T any] struct {
Data []T
}
func main() {
input := make([]KV[string, Item[string]], 0)
arr := MapFromSlice(input, func(input KV[string, Item[string]]) int {
return len(input.Value.Data)
})
// 首先思考下为什么需要 lambda,你会发现我们的参数类型 KV[string, Item[string]] 这么长,需要手动写,如果更复杂的呢?
// 而有了lambda或者有更加优秀的类型推断系统(类似于c++的auto/decltype关键字),可以避免这种一大串的类型申明
// MapFromSlice(input, (input)- > len(input.Value.Data))
fmt.Println(arr)
}
这里Go不支持lambda的原因很简单因为lambda本质上就是匿名函数(GO是支持的),但是有一点是lambda可以简化类型申明!
IDE支持不友好
类型推导不行 (goland 2023.3.2),需要手动写类型申明!
go
package main
import "fmt"
type List[T any] []T
func Map[Input, Output any](params List[Input], handler func(Input) Output) List[Output] {
ret := make(List[Output], 0, len(params))
for _, elem := range params {
ret = append(ret, handler(elem))
}
return ret
}
type KV[K comparable, V any] struct {
Key K
Value V
}
func main() {
input := make([]KV[string, int], 0)
input = append(input, KV[string, int]{}) // 推导不出来
array := Map(input, func(input KV[string, int]) int { // 推导不出来
return input.Value
})
for _, elem := range array {
fmt.Println(elem)
}
}
- 简单类型推导不出来
- 函数的参数也推导不出来
旧的API不太容易适配泛型
例如 sync.Map
类型,实际上它的泛型参数就一个 key/value ,如果Go官方如果把 sync.Map 改成了泛型实现,就会导致旧的API无法编译通过了!如果支持泛型默认值就好了!但是几百年了Go的函数参数都不支持默认值!想想也不太可能了!!
cpp
template <typename K = int, typename V = int>
struct Map {
// ...
};
int main() {
Map arr{};
Map<int, std::string> arr2{};
}
总结
- 可以减少代码量,提高代码的健壮性!
- 降低代码中使用
interface{}
类型,从而避免Go的逃逸带来的额外性能开销,进而提升代码的性能! - 泛型可以使得类型变得更加安全,即编译器可以帮我们做类型检查!
- Go的泛型整体设计比较简单遵循
less is more
,简单就是好,学习成本低,易用性高!