网易首页 > 网易号 > 正文 申请入驻

编程语言Zig有什么与众不同的

0
分享至

作者 | Erik Engheim

译者 | 马可薇

策划 | Tina

Zig 允许在编译期执行代码,这有什么意义?

Zig 的吉祥物“零号(Zero the Ziguana)”

编程语言专家曾对 Zig 编程语言的创造者 Andrew Kelley 说,在编译时运行代码是个蠢主意。尽管如此,Kelley 还是去实现了这个想法,而多年以后,这个蠢主意已经成为了 Zig 的招牌。这一特征在 Zig 中用关键字 comptime 标识,代表需要在编译时运行的代码或者是需要的变量。Zig 可以在编译时运行代码的能力让开发者们可以在不明确任何泛型或模板支撑的情况下,编写通用代码或是进行元编程。让我们来通过代码例子更直观地了解编译时运行是什么意思,以及其为什么重要。以这段简单的函数为例,在 a 和 b 两个数之间取最大值。不使用泛型或 comptime 代码的话,我们就需要将这个函数的具体变量类型写死,比如这里用的 Zig 中 32 位整数 i32 。


fn maximum(a: i32, b: i32) i32 {var result: i32 = undefined;
if (a > b) {result = a;} else {result = b;
return result;

和 C/C++ 一样,Zig 中可执行的程序通常都会有个 main 函数,我们可以在主函数里面调用最大值函数。在下面的代码,暂时不用管 stdout 的调用或者在 print 函数前的 try 关键词,后者和 Zig 的错误处理有关,在本文中并不涉及。


pub fn main() !void {const stdout = std.io.getStdOut().writer();
const a = 10;const b = 5;
const biggest = maximum(a, b);
try stdout.print("Max of {} and {} is {}\n", .{ a, b, biggest });

很明显,这个解决方案有很大局限性。首先,maximum 只能处理 32 位整数。C 语言编程者大概对这个问题并不陌生,C 预处理的宏就是用来解决这个问题的。Andrew Kelley 为避免依赖 C 的宏,专门设计了 Zig。可以说,Zig 存在的原因本质上就是 Andrew 想用 C 编程,但又不想折腾宏这类烦人的东西。comptime 的诞生的意义完全就是为了取代 C 的宏。

让我们再看看 Zig 对这类问题的解决方案。先在 Zig 中定义一个泛型 maxiumum 函数,用 anytype 和 @TypeOf(a) 替代 i32 类型参数。在 maximum 函数在被调用时,将默认 anytype 为提供的参数类型。请注意,Zig 不是动态编程语言,在用不同参数类型调用 maximum 时,Zig 的编译情况也会不同。a 和 b 的类型依旧会在编译时决定,而非运行时。

虽然在编译时确定输入参数的类型不是不行,但这么一来变量和返回类型就难处理了。anytype 不能用作是返回类型,因为我们不能在函数调用处再确定变量的具体类型。因此,我们需要用编译器内联函数 @TypeOf 在编译时生成返回类型,比如用 @TypeOf(a) 在编译时确定参数 a 的类型,或者是用来指定返回变量 result 的类型:


fn maximum(a: anytype, b: anytype) @TypeOf(a) {var result: @TypeOf(a) = undefined;
if (a > b) {result = a;} else {result = b;
return result;

虽然确实有了一定的提升,但还有别的问题:

  1. 没有限制用非数字参数调用 maximum 的情况

  2. 如果 b 值更大,那么返回值会有会超出 @TypeOf(a) 范围的情况

要想检测 a 和 b 的类型是否正确,我们可以创建一个在编译时运行的函数来检测参数是否是数字。定义函数 assertNumber 只有一个代表类型的参数 T,参数之前加上的 comptime,告诉编译器这是要在编译时必须已知的参数。

另外还需要注意下 switch 条件语句。在 Zig 里,switch 也可以返回数值,因此我们用参数 T 的类型做开关,如果 T 符合数字类型,那么 switch 条件语句就会返回 true,并将其赋给 is_num 变量。非数字类型则用 else 默认返回 false。


fn assertNumber(comptime T: type) void {const is_num = switch (T) {i8, i16, i32, i64 => true,u8, u16, u32, u64 => true,comptime_int, comptime_float => true,f16, f32, f64 => true,else => false,
if (!is_num) {@compileError("Inputs must be numbers");
// testing functionpub fn main() !void {assertNumber(bool);

在这个函数定义中另一个值得关注的点是 @compileError ,一个用来将编译器错误信息返回给用户的编译时内联函数。在这段代码中,我们给参数 assertNumber 提供了非数字的类型 bool,尝试编译这段程序后,我们会收到以下这段错误信息:


assert-number.zig:11:9: error: Inputs must be numbers@compileError("Inputs must be numbers");assert-number.zig:17:17: note: called from hereassertNumber(bool);assert-number.zig:16:21: note: called from herepub fn main() !void {

也就是说,我们可以在运行无效代码时,用代码本身给用户输出更加有价值的错误信息。下面让我们用 assertNumber 检查 maximum 函数的输入。为保证返回类型范围足够,我们可以让两个输入参数类型必须相同:


fn maximum(a: anytype, b: anytype) @TypeOf(a) {const A = @TypeOf(a);const B = @TypeOf(b);
assertNumber(A);assertNumber(B);
var result: @TypeOf(a) = undefined;
if (A != B) {@compileError("Inputs must be of the same type");
if (a > b) {result = a;} else {result = b;
return result;

在运行时调用 maximum 会替换用编译结果替换所有编译时代码。但目前这种解决方案还没有解决我们原始函数的所有问题。我们强制使 a 和 b 保持同样的类型,那么如果我们想要对比有符号的 8-bit 和有符号的 32-bit 整数,也就是 Zig 中的参数类型 i8 和 i32 呢?那么我们就必须保证返回类型是 i32,目前的方案并不能做到这一点。我们需要的是一个能够在编译时运行,对比 a 与 b 的类型,并返回最长比特类型的函数。

想做到这点,那么我们还需要以下两个函数:

  • nbits 函数,用于计算类型 T 的比特长度

  • largestType 函数,用于返回 A 和 B 两个类型中比特最长的一个

注意在下面的这个例子中我们用了 comptime 来标记参数的类型,以告知 Zig 这些输入在编译时必须已知,编译器内联函数 @typeInfo 用于在编译时返回用于描述类型的复合对象 info,其中包含了类型是否带符号,类型需要多少比特来表示的信息。


fn nbits(comptime T: type) i8 {return switch (@typeInfo(T)) {.Float => |info| info.bits,.Int => |info| info.bits,else => 64,
fn largestType(comptime A: type, comptime B: type) type {if (nbits(A) > nbits(B)) {return A;} else {return B;
fn maximum(a: anytype, b: anytype) largestType(@TypeOf(a),@TypeOf(b)) {var result: @TypeOf(a) = undefined;
if (a > b) {result = a;} else {result = b;
return result;

可能例子里的 switch 语句表示得不是很清楚,让我再解释下。@typeInfo(T) 所返回的类型是联合类型(union type)std.builtin.TypeInfo ,这种类型和结构(struct)有些相似,都包含多个共享内存的字段。因此我们需要使用 switch 条件语句找到具体是在使用.Int 还是.Float 字段。|info|语法在 Zig 中是用来解包数值的,在这里我们用它来找描述类型的结构。info 对象会有两种类型 TypeInfo.Int 或者 TypeInfo.Float,但这两种 struct 类型都会有一个 bits 字段。在我们改进后的 maximum 函数里,我们没有明确指定返回值,而是调用了 largestType 函数并将它的返回值用做了 maximum 返回值的类型。尽管看起来很怪,但这确实是可行的,因为 Zig 编译器在编译时调用 largestType 的确只依赖了已知信息。编译器会根据每次 maximum 的调用创建不同变体,对不同的输入类型和输出类型进行编译。

用编译时的代码实现泛型

Zig 中 comptime 的强大可以通过对泛型的实现来证明。在下面的例子中的 minimum 函数对习惯于泛型或基于模板编程的开发者来说很是熟悉。其中的关键区别在于,类型参数 T 是作为一般参数输入的。对于 C++、Java 和 C# 的开发者来说,这个函数一般会以 minimum (x, y) 的形式调用,但对于 Zig 开发者来说,minimum(i8, x, y) 足矣。


fn minimum(comptime T: type, a: T, b: T) T {assertNumber(T);
var result: T = undefined;if (a < b) {result = a;} else {result = b;
return result;

在 C/C++、Java 或 Swift 等语言中,我们通常可以从输入参数中推断变量类型。但在 Zig 中,这种类型推断不再可行,因为参数 T 被用作为一般参数,得不到特殊待遇了。虽然这让 comptime 弱势于泛型,但好处是 comptime 用起来更加灵活了。我们可以用 comptime 代码定义泛用类型,比如我们可以用 2D 矢量类来表示力、速度以及位置等信息。

查看英文原文:

What Makes the Zig Programming Language Unique? by Erik Engheim(https://erikexplores.substack.com/p/what-makes-the-zig-programming-language)

声明:本文为InfoQ翻译,未经许可禁止转载。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
涉嫌严重违纪违法,付天军被查

涉嫌严重违纪违法,付天军被查

都市快报橙柿互动
2026-08-02 23:15:36
C罗账户少百万美金!16岁迷你罗偷钱讨好女友+严父暴怒背后藏着啥

C罗账户少百万美金!16岁迷你罗偷钱讨好女友+严父暴怒背后藏着啥

绿茵八卦君
2026-08-03 07:30:03
SpaceX首次财报本周重磅登场!除业绩外,市场还将关注这些……

SpaceX首次财报本周重磅登场!除业绩外,市场还将关注这些……

财联社
2026-08-03 13:38:05
快讯!郭德纲高调发文了!

快讯!郭德纲高调发文了!

故事终将光明磊落
2026-08-03 08:10:54
冉莹颖回击败家传闻,自曝收入是邹市明五倍,承担大部分养家重担

冉莹颖回击败家传闻,自曝收入是邹市明五倍,承担大部分养家重担

麦芽是个小趴菜
2026-08-03 04:26:30
化妆师陶容桂去世,年仅42岁,其女儿悲痛发讣告,前后仅2年

化妆师陶容桂去世,年仅42岁,其女儿悲痛发讣告,前后仅2年

180视角
2026-08-03 08:42:58
皇马连遭重创!1.2亿欧签迪奥曼德或告吹 曼城拒绝降价出售罗德里

皇马连遭重创!1.2亿欧签迪奥曼德或告吹 曼城拒绝降价出售罗德里

风过乡
2026-08-03 05:55:06
你在ktv遇见最恶心的客人是啥样?网友:稍稍有点辣眼睛,纯洁的慎看

你在ktv遇见最恶心的客人是啥样?网友:稍稍有点辣眼睛,纯洁的慎看

解读热点事件
2026-08-01 00:05:21
22岁女孩程某圆失联第六天,三次能救自己的机会,她全都错过了?

22岁女孩程某圆失联第六天,三次能救自己的机会,她全都错过了?

黑哥讲现代史
2026-08-02 14:59:46
恭喜广东队!CBA第一内线有望加盟,曾被杜锋点名要人?

恭喜广东队!CBA第一内线有望加盟,曾被杜锋点名要人?

绯雨儿
2026-08-03 10:56:25
男子晒5辆豪车、179万余额,还自称是浙江一大公司老板的儿子!知情人士:他的真实身份你意想不到…

男子晒5辆豪车、179万余额,还自称是浙江一大公司老板的儿子!知情人士:他的真实身份你意想不到…

台州交通广播
2026-08-01 11:24:15
日本发出强硬警告,如果中国继续实施导弹试射,我们就加速拥核!

日本发出强硬警告,如果中国继续实施导弹试射,我们就加速拥核!

棠棣分享
2026-08-03 12:40:15
震惊!比亚迪再次“封神”,马斯克真笑不出来了

震惊!比亚迪再次“封神”,马斯克真笑不出来了

大厂财经社
2026-08-03 11:02:50
40℃高温实测:油车稳如泰山,电车分三六九等

40℃高温实测:油车稳如泰山,电车分三六九等

趣味萌宠的日常
2026-08-03 00:40:19
武统、和统都没希望了?祖国统一进入深海区,中国走出第3条道路

武统、和统都没希望了?祖国统一进入深海区,中国走出第3条道路

历史点行
2026-08-03 15:25:41
16岁已是人间尤物,4年换19个男人,找到老实人接盘后收心做人妻

16岁已是人间尤物,4年换19个男人,找到老实人接盘后收心做人妻

凡知
2026-07-21 15:26:17
刚刚 | 4.2级!四川今日连发6次地震!

刚刚 | 4.2级!四川今日连发6次地震!

天津广播
2026-08-03 13:45:26
午评:科创50指数半日跌3.73% 沪深两市成交额半日萎缩超4200亿元

午评:科创50指数半日跌3.73% 沪深两市成交额半日萎缩超4200亿元

每日经济新闻
2026-08-03 11:40:22
男子癌症晚期治疗时发现医保断缴两年 没法报销 当地回应:未发现私吞医保缴费资金 可能漏缴

男子癌症晚期治疗时发现医保断缴两年 没法报销 当地回应:未发现私吞医保缴费资金 可能漏缴

闪电新闻
2026-08-03 12:01:54
巴萨截胡罗德里?皇马早已先下一城说实话,看到“巴萨可能截胡罗德里”这个消息的时候,我愣了一下

巴萨截胡罗德里?皇马早已先下一城说实话,看到“巴萨可能截胡罗德里”这个消息的时候,我愣了一下

带你逛体坛
2026-08-03 15:23:27
2026-08-03 16:07:00
InfoQ incentive-icons
InfoQ
有内容的技术社区媒体
12747文章数 52014关注度
往期回顾 全部

科技要闻

小米多款手机涨价,店员:周六晚接到通知

头条要闻

"投资100万最少拿回400万"无法兑现 "投资者"当场昏倒

头条要闻

"投资100万最少拿回400万"无法兑现 "投资者"当场昏倒

体育要闻

常规赛MVP可以衡量常规赛成就吗?

娱乐要闻

奥德赛女主持递麦事件争议又升级了

财经要闻

厦门象屿青岛大火背后

汽车要闻

00后搞钱成功后的第一台车,真的不是BBA

态度原创

房产
健康
教育
手机
亲子

房产要闻

1700亿砸下!信息量巨大!海南甩出又一个超级规划!

中风易复发!谈中风康复与二级预防

教育要闻

as well as、as well、as long as还在混着用?一张“家庭关系图”让你一次分清,再也不会错!

手机要闻

2026年Q2全球手机市场营收同比增长7%:苹果营收份额达49%

亲子要闻

父母是我们最大的命运,你自己才是成长的答案

无障碍浏览 进入关怀版