有了C++、D和Rust,为什么还需要Zig?

没有隐式控制流

如果 Zig 代码看起来不像是在调用一个函数,那么它就不是。这意味着你可以确定下面的代码只会先调用 foo(),然后调用 bar(),不需要知道任何元素的类型,这一点也是可以保证的:

var a = b + c.d;
foo();
bar();

隐式控制流的例子:

这个设计决定的目的是可读性。

没有隐式内存分配

Zig语言不干预堆内存分配。没有new关键字或其他任何使用堆分配器的语言功能(例如字符串连接运算符[1])。整个堆都是由库或者用户代码而非语言本身所管理的。

隐式内存分配的例子:

几乎所有的包含垃圾收集的语言都充满了隐式内存分配,不过垃圾收集器把隐式内存分配的证据隐藏在清理的那一侧。

隐藏内存分配的主要问题在于,它阻止了一段代码的可重用性,从而不必要的限制了适合代码部署的环境数量。简而言之,在某些用例中,必须能依赖于控制流和函数调用不产生内存分配的副作用,因此,一门语言必须能在切实提供这些保证的情况下才能为这些用例提供服务。

在Zig中,有一些标准库功能提供了堆分配器并且可以配合堆分配器,但这些都是可选的标准库特性,而不是内置在语言本身中的。如果你从不初始化堆分配器,那么你可以确信你的程序永远不会引起堆分配。

每一个需要分配内存的标准库特性都会接受一个分配器参数来进行内存分配。这意味着Zig的标准库特性支持裸金属目标。例如 std.ArrayListstd.AutoHashMap都可以用于裸金属编程!

自定义内存分配器使得手动管理内存变得轻而易举。Zig有一个调试目的的分配器,可以在“释放后使用”和“双重释放”的情况下保证安全性。它能自动检测,并在内存泄露的时候打印堆栈跟踪;还有一个Arena分配器,可以让你将多个分配请求合并成一个,并统一释放,而不是独立的释放。特殊用途的分配器可以用来提高性能或内存的使用,以满足任何特定应用程序的需要。

[1]:事实上有一个编译期字符串连接运算符(广义来说,是数组连接运算符),但它只能在编译期使用,所以仍然没有运行时的堆分配。

无标准库的一流支持

如上所述,Zig具有完全可选的标准库。每个标准库API仅在使用时才会编译到你的程序中。Zig同时支持链接或不链接libc。因此Zig非常适合裸机和高性能开发。

这是两全其美的。例如在Zig中,与支持编译为WebAssembly的其他编程语言相比,WebAssembly程序既可以使用标准库的常规功能,又可以生成最小的二进制文件。

为库设计的可移植语言

编程的圣杯之一是代码重用。遗憾的是,在实践中我们发现自己多次重复发明轮子。很多时候这是有理由的:

为现有项目的构建系统和包管理器

Zig 是一门编程语言,但它也提供了一个构建系统和包管理器,即使在传统的C/C++项目中也很有用。

你不仅可以用Zig代码代替C或C++代码,还可以用Zig代替autotools、cmake、make、scons、ninja等。而在此之上,Zig(将)提供一个本地依赖的包管理器。这个构建系统的目的是为了使得即使一个项目的全部代码库都是C或C++也能适用。

apt-get、pacman、homebrew等系统包管理器对最终用户的体验很有帮助,但它们可能不足以满足开发人员的需求。有无语言专用的包管理器可以形成没有贡献者和有大量贡献者之间的差距。对于开源项目来说,项目构建的难度对潜在贡献者来说是一个巨大的障碍。特别是对于C/C++项目来说,依赖关系可能是致命的,尤其是在没有包管理器的Windows上。即使只是构建Zig本身,大多数潜在的贡献者在LLVM依赖上也会遇到困难。Zig(将)为项目提供一种直接依赖原生库的方式——不需要依赖用户的系统包管理器来获得正确的版本,而且这种方式几乎可以保证不管使用的是什么系统,也不管目标平台是什么,都可以在第一次尝试时就成功地构建项目。

Zig用一种使用声明式API的合理的语言来代替项目的构建系统。它还提供包管理系统,从而可以依赖其他C库。有了声明依赖的能力,就能实现更高层次的抽象,从而实现可重用高级代码的大量涌现。

简单性

C++、Rust和D有大量的特性,它可能会打乱你正在编写的应用程序的实际含义。人们发现自己是在调试自己的编程语言知识,而不是调试应用程序本身。

Zig没有宏也没有元编程,但仍然足够强大,可以清晰、不重复地表达复杂的程序。即使是在有宏的 Rust 里, format! 也是特例,它是在编译器内部实现的。与此同时Zig中的等价函数是在标准库中实现的,编译器中没有特例代码。

工具性

可以从下载页面下载Zig。Zig提供了Linux、Windows、MacOS和FreeBSD的二进制存档。你将得到: