Zig vs Go:泛型
发布: (2026年2月10日 GMT+8 07:07)
4 分钟阅读
原文: Dev.to
Source: Dev.to
介绍
Go 在 1.18 版中引入了泛型,允许函数和结构体按类型进行参数化。Zig 长期通过 comptime 关键字支持编译时泛型,实现了零运行时开销。
函数泛型
Go
func doubleNumber[T constraints.Integer | constraints.Float](a T) T {
return a * T(2)
}
doubledFloat := doubleNumber[float32](5.3)
fmt.Println(doubledFloat)
// type parameter inferred
doubledInteger := doubleNumber(5)
fmt.Println(doubledInteger)
Zig
fn doubleNumber(comptime T: type, a: T) T {
return a * 2;
}
const doubledFloat = doubleNumber(f32, 5.3);
std.debug.print("{}\n", .{doubledFloat});
const doubledInteger = doubleNumber(i32, 5);
std.debug.print("{}\n", .{doubledInteger});
在 Go 中,泛型受接口约束,编译器在编译时进行检查。Zig 在为提供的类型实例化函数时,会在编译期阶段评估任何错误。
结构体泛型
Go
type Stack[T any, Q any] struct {
Items []T
AlterItems []Q
Index int
}
intStack := Stack[int8, float32]{
Items: []int8{1, 2, 3, 4, 5},
AlterItems: []float32{1.1, 2.1, 3.1, 4.7, 5.3},
Index: 4,
}
fmt.Println(intStack.Items[intStack.Index], intStack.AlterItems[intStack.Index])
strStack := Stack[string, bool]{
Items: []string{"hello", "gophers"},
AlterItems: []bool{false, true},
Index: 0,
}
fmt.Println(strStack.Items[strStack.Index], strStack.AlterItems[strStack.Index])
Zig
fn Stack(comptime T: type, comptime Q: type) type {
return struct {
items: []const T,
alterItems: []const Q,
index: usize,
};
}
const intArr = [_]i32{ 1, 2, 3, 4, 5 };
const floatArr = [_]f32{ 1.1, 2.1, 3.1, 4.7, 5.3 };
const intStack = Stack(i32, f32){
.items = intArr[0..],
.alterItems = floatArr[0..],
.index = 4,
};
std.debug.print("{d} {d}\n", .{
intStack.items[intStack.index],
intStack.alterItems[intStack.index],
});
const strArr = [_][]const u8{ "hello", "ziguanas" };
const boolArr = [_]bool{ false, true };
const strStack = Stack([]const u8, bool){
.items = &strArr,
.alterItems = &boolArr,
.index = 1,
};
std.debug.print("{s} {}\n", .{
strStack.items[strStack.index],
strStack.alterItems[strStack.index],
});
约束 vs. 接口
在 Go 中,约束表现为接口。当泛型函数接受一个结构体时,该结构体必须实现函数内部使用的方法。
type IdTrackable interface {
GetID() string
}
type Article struct {
ID string
Name string
Category string
}
func (a Article) GetID() string { return a.ID }
type SKU struct {
ID string
ArticleID string
Available bool
}
func (s SKU) GetID() string { return s.ID }
func findById[T IdTrackable](items []T, id string) (*T, error) {
for _, item := range items {
if item.GetID() == id {
return &item, nil
}
}
return nil, errors.New("not found")
}
示例:按 ID 查找
Go 用法
articles := []Article{
{ID: "a1", Name: "Laptop", Category: "Electronics"},
{ID: "a2", Name: "Book", Category: "Education"},
}
article, err := findById(articles, "a1")
if err == nil {
fmt.Printf("Found article: %+v\n", *article)
}
Zig 实现
const Article = struct {
id: []const u8,
name: []const u8,
category: []const u8,
};
const SKU = struct {
id: []const u8,
articleId: []const u8,
available: bool,
};
fn findById(comptime T: type, items: []const T, idx: []const u8) !T {
for (items) |item| {
if (std.mem.eql(u8, @field(item, "id"), idx)) {
return item;
}
}
return error.NotFound;
}
const articles = [_]Article{
.{ .id = "a1", .name = "Laptop", .category = "Electronics" },
.{ .id = "a2", .name = "Book", .category = "Education" },
};
const art = try findById(Article, &articles, "a2");
std.debug.print("Found article: {}\n", .{art});
在 Zig 中,泛型函数会在编译时针对每种不同的类型实例化,生成各自的专门化版本,且没有运行时开销。
结论
Zig 的编译时元编程提供了一种强大、零开销的泛型实现方式,而 Go 的泛型依赖于通过接口约束的运行时免费类型参数。两种语言都实现了类型安全、可重用的代码,但 Zig 提供了更广泛的元编程能力,超出了 Go 目前支持的范围。