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 目前支持的范围。

0 浏览
Back to Blog

相关文章

阅读更多 »

从 Zig 学到的经验

Zig 编程语言保持有意小的标准库。不符合严格收录标准的组件会被删除并重新定位……

什么是泛型?

Generics 是在 Java 5 中引入的一项功能,允许创建能够处理不同数据类型的类、接口和方法。它们消除……

Go 模板

什么是 Go 模板?Go 模板是一种在 Go 中通过将数据与纯文本或 HTML 文件混合来创建动态内容的方式。它们允许您替换占位符……