Redian新闻
>
编程语言Zig有什么与众不同的

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

公众号新闻

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

今日好文推荐

反转!马斯克正在求被裁工程师复职,尤其是Android和iOS开发

苹果暂停除研发外岗位招聘,市值一夜蒸发7160亿元;腾讯和联通合资公司因为云计算;国美停发工资,要求员工签理解承诺书|Q资讯

阿里巴巴开源下一代云原生网关Higress:基于Envoy,支持Nginx Ingress零成本快速迁移

马斯克整顿西方职场,Twitter员工突然进入“黑色星期五”:办公室关闭,裁员名单确认前不必上班

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
冯仑:追求理想过程中的人生体验,会让你与众不同【双养日课 1029】人际关系的烦恼源于与众不同的私人逻辑想要明年继续赚钱,抓住这只与众不同的能源股你知道在英国,如何正确的分类垃圾吗?不同的垃圾有不同的收取时间哦!AACL'22 Best Paper | 不同的表达可能会引发读者不同的想法,可以通过模型自动模拟这种语言“偏见”一行代码12倍加速Bert推理,OpenAI编程语言加持的引擎火了鸿运从脚生!红红火火的新年怎能少了与众不同的兔年红袜一日禅 | 为独特的自己骄傲,对与众不同的他人接纳包容万圣节与众不同是件很正常的事!“萌鬼游园会”就是这么“不正常”“要想无可取代,就必须与众不同”中文编程不如英文香?今年诞生的这些国产编程语言表示不服由《隐入尘烟》说到电影《武训传》的随想篇触摸美国 54 收获季节当年俄罗斯人为什么会拥抱普京(ZT)你是许半夏,还是赵丽颖?不同的角色不同的你,加油!吸睛|住灯塔里、住监狱里、住铁轨旁!盘点加拿大最与众不同酒店,太有吸引力了你的与众不同,或许是你独一无二的天赋分析了1000万个开发岗位后,我发现了今年收入最高的十大编程语言最与众不同的13所美国大学英国女王辞世,突然想起了慈禧太后不同的教育方式,铸造不同的人生[日签] 你天生与众不同,不必躲躲藏藏【UIUC】你觉得是什么让UIUC与众不同?芝加哥艺术学院(三)塞尚(上)真我是我 我本与众不同分析了 1000 万个开发岗位后,我发现了今年收入最高的十大编程语言这样的新年祝福,与众不同计算机编程的历史演进:用 50 种编程语言写 “Hello,World!” 程序包校→美高女校→哈佛,我如何在“卷王”中杀出一条与众不同的升学路?硬核观察 #870 C 语言已不再仅仅是一种编程语言如何使用Rust语言设计并开发一个领域编程语言“妈妈,我想买”,父母不同的回应方式,造就孩子不同的性格【科普】你觉得是什么让UIUC与众不同?酷炫朋克机甲风蓝牙耳机,开盖秒连,带给你与众不同的听觉盛宴编程语言鄙视链,学错了一年少赚几十万!
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。