《泛智能开启下一代云时代》白皮书来了!点击下载 了解详情
写点什么

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

  • 2022-11-10
    北京
  • 本文字数:3720 字

    阅读完需:约 12 分钟

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

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 here    assertNumber(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 翻译,未经许可禁止转载。


2022-11-10 19:226268

评论

发布
暂无评论
发现更多内容

架构训练营 - 模块四 - 作业

姑射仙人

架构训练营

【Takin应用日记】记一次TransmittableThreadLocal引起的业务异常

TakinTalks稳定性社区

高可用 性能压测 生产环境全链路压测 takin

博睿数据分布式手机真机监测+两大核心技术,轻松掌控短信服务质量与用户体验

博睿数据

微博SDK初始化问题 please init sdk before use it. Wb.install()

mengxn

微博sdk

冲击“金九银十”的利器!《Java权威面试指南(阿里版)》人手一份吊打面试官轻轻松松!

Java 编程 IT 计算机 知识分享

运维工程师核心工作是什么?用什么运维工具好?

行云管家

云计算 运维 IT运维

Aosp 之 Property

Qunar技术沙龙

android API properties 字典树 内存映射

TCP 四次挥手

W🌥

计算机网络 TCP/IP 8月日更

啃了三个月!靠着这份大厂Java面试全秘籍,成功入职京东,税前30K

Java 程序员 架构 面试 计算机

DCS_FunTester分布式压测框架更新(二)

FunTester

分布式 性能测试 测试框架 测试开发 FunTester

疫情在家“闭关修炼”,读完这些Java技术栈,愿金三银四过五斩六

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

远程办公一星期,竟等来了阿里新零售视频面(Java岗,已过2面)

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

小心这个陷阱:为什么总是你赔钱?

非著名程序员

认知提升 个人提升 投资理财 8月日更

GitHub星标63K霸榜半月!阿里大牛的微服务分布式架构笔记已上线

Java 编程 IT 计算机 知识

一个完整的内网渗透是什么样子的

网络安全学海

网络安全 信息安全 网络 渗透测试 漏洞分析

深耕城市治理场景,百度智能云联合慧联无限推内涝智能检测预警

百度大脑

人工智能 洪水

啃完这些Spring知识点,我竟吊打了阿里面试官(附面经+笔记

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

如何在多云环境中建立信任

浪潮云

云计算

滴滴架构师被迫离职后,只留下这份731页Java程序性能优化手册

Java 编程 架构 面试 调优

云服务器在市场变化下的技术突破,企业运维中的基础保障

九河云安全

APP动态换肤方案详解

Geen练

ios App theme 换肤

嗨!你有一封来自百度世界大会的“情书”,818等你开启

百度大脑

人工智能

极客星球 | Android SDK架构设计之路

MobTech袤博科技

架构 sdk andiod

通俗易懂的ReentrantLock,不懂你来砍我

程序猿阿星

AQS 公平锁 非公平锁 独占锁 ReentrantLock;

中国如何应对中美博弈?

石云升

学习 贸易战 8月日更

去中心化DeFi系统开发

Geek_23f0c3

智能合约 DeFi去中心化系统开发 DAPP智能合约交易系统开发

【共识专栏】HotStuff共识

趣链科技

区块链 共识机制 拜占庭容错 共识算法

肺炎在家“闭关”,阿里竟发来视频面试,4面顺利拿下offer

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

iOS 开发技术栈与进阶

iOSer

ios 面试 iOS 知识体系 iOS技术栈

如何快速定位程序Core?

百度Geek说

Linux 后端

如何实现H.264的实时传输?

拍乐云Pano

  • 扫码添加小助手
    领取最新资料包
编程语言Zig有什么与众不同的_开源_Erik Engheim_InfoQ精选文章