也来谈谈golang泛型

我们都知道golang在1.18版本之后引入了泛型,这对广大因不支持泛型饱受重复代码之苦的gophers喜大普奔。笔者在最近一个项目中尝鲜了泛型特性。总的来说,1.18中引入泛型相比之前版本是一个巨大的进步,但同时也应当看到,golang泛型的表达能力相比c++/rust等语言还是有不小的差距,这种差距也大大影响了golang泛型的表达能力。本文将简要介绍golang泛型的使用和一些目前尚不支持的功能。

泛型使用

闲话少说,看代码就明白了:


/// 定义类型合约Addable
/// 只能是int或string类型或它们的alias类型(如type Name string)
type Addable interface {
	~int | ~string
}

// myAdd函数的参数类型必须满足合约Addble
func myAdd[T Addable](a, b T) T {
	return a + b
}

func main() {
	var (
		ia int = 1
		ib int = 2
	)
	/// 编译器自动推导参数类型T = int
	fmt.Println(myAdd(ia, ib)) // 输出3

	type Name string
	var (
		sa Name = "hello"
		sb Name = " world"
	)
	/// 编译器自动推导参数类型T = Name = ~string
	fmt.Println(myAdd(sa, sb)) // 输出hello world
}

除了支持函数泛型之外,golang还支持interface和struct的泛型化,对标c++的模板类。

好了,golang泛型的使用介绍到此为止了。接下来咱就要列举golang泛型的"两宗罪"了。相信未来go社区会很快补齐这些短板,不断增强泛型的表达能力。

不支持泛型特化

泛型特化在c++中是及其常规的操作。正是因为有了泛型特化,c++才能实现如此多的Traits特性。一旦使用了泛型,某个函数或类针对每一个具体类型都要有对应的实现,具体类型之间既有共性(它们都满足某个类型合约)又有差异性(例如int和[]byte的相加实现不相同)。为了解决这种差异性,c++中支持了泛型特化,而这正是go泛型所欠缺的。

cpp支持的泛型特化

#include <iostream>
#include <cstring>

/// C++20中引入了concept可实现对于golang type contract相同的效果
/// 这里为了简化问题未使用concept
template <class T>
T myAdd(T a, T b)
{
    return a+b;
}

/// 针对char *类型的特化
template <>
char * myAdd(char * a, char *b)
{
    return strncat(a, b, strlen(b));
}


int main()
{
    std::cout << myAdd(1, 2) << std::endl; // 输出3

    char a[16] = "hello";
    char b[16] = " world";
    std::cout << myAdd(a, b) << std::endl; // 输出hello world
    return 0;
}

golang不支持的泛型特化

/// 合约新增一种类型: []byte
type Addable interface {
	~int | ~string | []byte
}

/// 针对[]byte的特化实现
func myAdd[T []byte](a, b T) {
	a = append(a, b...)
	return a
}

func myAdd[T Addable](a, b T) T {
	return a + b
}

/* 编译报错
./main.go:16:6: myAdd redeclared in this block
        ./main.go:11:6: other declaration of myAdd
./main.go:17:9: invalid operation: operator + not defined on a (variable of type T constrained by Addable)
*/

不支持类方法使用泛型

c++支持类的方法使用泛型


class A
{
public:
    template <class T>
    T add(T a, T b)
    {
        return a+b;
    }
};

int main() 
{
    int a = 1, b = 2;
    A o;
    std::cout << o.add(a, b) << std::endl;
}

golang不支持类的方法使用泛型


type Addable interface {
	~int | ~string
}

type A struct{
}

func (a *A) Add[T Addable](a, b T) T{
	return a+b
}

func main()
{
	o := A{};
	var (
		a int = 1
		b int = 2
	)
	o.Add(a, b)
}

/*
编译报错:
./main.go:12:16: syntax error: method must have no type parameters
./main.go:17:1: syntax error: unexpected semicolon or newline before {
./main.go:23:2: syntax error: non-declaration statement outside function body
*/

总结

本文简要介绍了golang泛型如何使用,然后将其与c++模板对比,指出golang泛型亟待补足的两块短板。

参考

https://go.dev/doc/tutorial/generics