【译文】既然已经有了 C++、D 和 Rust,为什么还要 Zig?

没有隐式控制流

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

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

隐式控制流的例子:

  • D有 @property 函数,可以让你的方法调用看起来像是成员访问,因此在上面的例子中,c.d可能会调用一个函数。
  • C++,D和Rust有运算符重载,因此+可能会调用一个函数。
  • C++,D和Go可以抛出和捕获异常,因此foo()可能会抛出一个异常,并且将会阻止bar()被调用。(当然,即使在 Zig 中foo()也可能存在死锁,并阻止 bar() 被调用,但是这可能发生在任何一个图灵完备的语言里。)

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

没有隐式内存分配

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

隐式内存分配的例子:

  • 在Go中,defer 关键字在函数本地栈中分配内存。不仅是一种不直观的控制流,而且一旦在循环中使用 defer ,还可能因内存不足而失败。
  • 在C++中,coroutine特性需要分配堆内存以调用。
  • 在Go中,goroutine分配的栈因调用栈变深而调整大小,因而调用函数就可能引发内存分配。
  • 在Rust中,标准库API会因为内存不足而崩溃。接受分配器参数的替代API尚在讨论见 rust-lang/rust#29802)。

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

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

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

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

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

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

无标准库的一流支持

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

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

为库设计的可移植语言

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

  • 如果一个应用程序有实时性需求,那么任何使用垃圾收集或任何其他非确定性行为的库都将不予考虑。
  • 如果一门语言让人太容易忽略错误,因此不得不验证一个库是否正确地处理和抛出错误,就很容易因此放弃这个库并重新实现它,只有自己知道自己正确地处理了所有相关的错误。Zig的设计使程序员能做的最懒的事情就是正确处理错误,因此人们可以合理地相信一个库会正确地抛出错误。
  • 目前,实事求是的说,C语言是最通用、最可移植的语言。任何不具备与C代码互操作能力的语言都有可能被历史所抛弃。Zig试图成为编写库的新的可移植语言,同时使导出函数直接符合C ABI,并引入安全性和防止实现中的常见错误的语言设计。

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

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的二进制存档。你将得到:

  • 通过下载并解压单个压缩包进行安装,无需配置系统
  • 静态编译,没有运行时依赖
  • 使用成熟的、得到良好支持的LLVM基础架构,支持大多数主要平台,并进行深度优化
  • 开箱即用的支持大多数主要平台上交叉编译
  • 提供libc的源代码,在任何支持的平台上需要时都会动态编译
  • 包括带缓存的构建系统
  • 编译具有libc依赖的C/C++项目

本文文字及图片出自 Why Zig When There is Already C++, D, and Rust?

余下全文(1/3)
分享这篇文章:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注