首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >关于 Go、Rust 和 Zig 的一些想法(译)

关于 Go、Rust 和 Zig 的一些想法(译)

作者头像
JanYork_简昀
发布2026-02-04 14:17:12
发布2026-02-04 14:17:12
1960
举报

2025 年 8 月 9 日

原文 https://sinclairtarget.com/blog/2025/08/thoughts-on-go-vs.-rust-vs.-zig

作者 Sinclair Target


我最近意识到,自己并不是在践行“为任务选择最合适的工具”,而是在使用“我在工作里正好用得上的工具”,而这几乎决定了我会哪些编程语言。

所以在过去几个月里,我花了不少时间去试验一些在工作中用不到的语言。目标并不是熟练掌握;我更感兴趣的是形成一套判断:每门语言究竟擅长什么。

编程语言在太多维度上各不相同,以至于比较它们时,很容易滑向一个显而易见、却无比无聊且并不太有帮助的结论:存在权衡。

是的,当然有权衡。更重要的问题是,为什么这门语言会选择并坚持这组特定的权衡?

这个问题之所以让我着迷,是因为我不想像买加湿器一样,拿着一张功能清单去挑语言。我关心的是做软件,也关心我的工具。

语言在做出这些取舍时,其实表达了一套价值观。我想弄清楚,哪些价值观会与我共振。

这个问题也能帮助我们区分那些“说到底特性高度重叠”的语言。

要是把网上关于“Go vs. Rust”或“Rust vs. Zig”的提问数量当作指标,人们显然很困惑。要记住“语言 X 更适合写 Web 服务,因为它有特性 abc,而语言 Y 只有 ab”并不容易。倒不如记住:语言 X 更适合写 Web 服务,是因为语言 Y 出自一个讨厌互联网的人(假设如此),他甚至认为我们应该把网线全拔了。

这篇文章里我汇总了我近来试用的三门语言,Go、Rust 和 Zig。

我试着把每门语言带给我的体验,凝练成一个大而化之的裁断,这门语言重视什么?以及它把这些价值观贯彻得如何?

这当然可能过于武断,但老实说,把一堆武断的偏见具体化,差不多正是我写这篇东西的目的。

Go

Go 最鲜明的气质是极简。有人把它称为“现代 C”。

Go 当然不是 C,它有垃圾回收,也有真正的运行时,但它又确实像 C,因为你可以把整门语言装进脑子里。

之所以能把它装进脑子里,是因为 Go 的特性很少。

很长一段时间里,Go 因为没有泛型而声名狼藉。直到 Go 1.18 这件事才终于改变。

那是在人们恳求了整整 12 年之后。

许多现代语言常见的特性,比如带标签联合体(tagged union)或用于错误处理的语法糖,至今也没有进入 Go。

看起来 Go 团队为 “往语言里加东西” 设了很高的门槛。

结果是,为了实现一些在别的语言里可以更简洁表达的逻辑,你不得不在 Go 里写大量样板代码。可另一面也同样成立,它因此长期稳定、易于阅读

再举一个体现 Go 极简主义的例子,Go 的切片(slice)类型。

Rust 和 Zig 也都有 slice,但它们只是一种胖指针(fat pointer),仅此而已。

在 Go 里,slice 是指向内存中一段连续区域的胖指针;但它还能增长,这意味着它把 Rust 的 Vec<T> 与 Zig 的 ArrayList 这类动态数组容器的职责也一并吞了。

再加上 Go 会替你管理内存,你的 slice 底层那块数组究竟在栈上还是堆上,由 Go 来决定;而在 Rust 或 Zig 里,你得更费力地思考内存到底“住”在哪里。

据我理解,Go 的“起源神话”大致是这样,Rob Pike 受够了等 C++ 项目编译,也受够了 Google 的同事们在那些 C++ 项目里不停犯错。所以在 C++ 繁复得近乎巴洛克的地方,Go 选择了克制与简洁。

它是一门面向“写代码的大多数人”的语言,设计目标是覆盖 90% 的使用场景,同时仍然容易理解,甚至(也许尤其是)在写并发代码时也是如此。

我在工作中并不用 Go,但我觉得我应该用。

Go 的极简,是在为企业协作服务。我并不是在贬低它,在公司环境里做软件有自己的难题,而 Go 恰好能把这些难题处理得很妥帖。

Rust

如果说 Go 是极简主义,那 Rust 就是极繁主义。

人们常给 Rust 配一句标语 “零成本抽象”。我更愿意把它改成 “零成本抽象,而且有一大堆!”

Rust 一直以难学著称。

我同意 Jamie Brandon 的说法,让 Rust 困难的不是生命周期[1],而是语言里被塞进了太多概念。我也不是第一个拿那条 GitHub 评论[2] 开涮的人,但它确实把 Rust 的“概念密度”展示得淋漓尽致:

类型 Pin<&LocalType> 实现了 Deref<Target = LocalType>,但没有实现 DerefMut。 类型 Pin&#[fundamental],因此可以为 Pin<&LocalType>> 写一个 impl DerefMut。 你可以用 LocalType == SomeLocalStructLocalType == dyn LocalTrait,并且可以把 Pin<Pin<&SomeLocalStruct>> 强制转换成 Pin<Pin<&dyn LocalTrait>>。 (没错,两层 Pin!!) 这使得我们可以在稳定版上创建一对“实现了 CoerceUnsized 但行为古怪”的“智能指针”(Pin<&SomeLocalStruct>Pin<&dyn LocalTrait> 会成为这些“行为古怪”的智能指针,而且它们已经实现了 CoerceUnsized)。

当然,Rust 并不是在用“与 Go 相反”的方式去极繁。

Rust 之所以复杂,是因为它想同时兑现两个彼此存在张力的目标,安全与性能

性能的含义不言自明。

“安全”就没那么直观了,至少对我来说如此,可能是我被 Python 养坏脑子太久了。

“安全” 指的是 “内存安全” ,你不应该能解引用一个无效指针,也不应该能二次释放(double-free),等等。

但它的含义还不止如此。所谓“安全”的程序,还要避免一切未定义行为(undefined behavior,常写作 UB)。

那令人闻风丧胆的 UB 到底是什么?

我觉得最好的理解方式,是记住,对一个正在运行的程序来说,有些结局比死亡更糟。

如果程序出了错,立刻崩溃其实挺好!

因为更可怕的替代方案是,错误没被捕获,程序就跨入一片不可预测的暮色地带,它的行为可能取决于下一场数据竞争里哪条线程赢了,或者取决于某个内存地址上碰巧有什么垃圾。

于是你会得到海森堡 bug(heisenbug)与安全漏洞。

这非常糟糕!

Rust 试图在不付出任何运行时性能代价的前提下,通过编译期检查来杜绝 UB。

Rust 编译器很聪明,但它不是全知全能的

要检查你的代码,它必须能“理解”你的代码在运行时会做什么。因此 Rust 具备一个表达力很强的类型系统,以及一整座动物园般的 trait 体系,让你能把在其他语言里只是“代码运行起来大概就那样”的行为,用编译器能看懂的方式表达出来。

这让 Rust 变难了,因为你不能直接把事情做了就完!

你得先找到 Rust 对这件事的命名,找到你需要的 trait 或其他机制,再按 Rust 期待的方式把它实现出来。

但一旦你做到了,Rust 就能对你的代码行为做出许多其他语言做不到的保证,而这在某些应用里可能至关重要。

它也能对 “别人写的代码” 做出保证,这让你在 Rust 里使用第三方库变得更轻松,也解释了为什么 Rust 项目的依赖数量几乎和 JavaScript 生态一样夸张。

Zig

在这三门语言里,Zig 最新、也最不成熟。

写下这些文字时,Zig 还停留在 0.14 版本。它的标准库几乎没有文档,学习它最靠谱的方法往往是直接去读源码。

我不知道这是不是事实,但我喜欢把 Zig 想象成对 Go 与 Rust 的一种回应,Go 的简单,是因为它把 “计算机究竟怎么工作”的细节遮蔽起来;Rust 的安全,是因为它逼你一圈又一圈地跳火圈;Zig 会把你解放出来!

在 Zig 里,你掌控整个宇宙,没有人能告诉你该怎么做。

在 Go 和 Rust 里,把一个对象分配到堆上,简单到只要从函数里返回一个指向结构体的指针就行。

分配是隐式发生的

到了 Zig,你要亲手、显式地分配每一个字节(Zig 是手动内存管理)。

而且你在这里拥有的控制力甚至超过 C,要分配内存,你必须在某一种具体的分配器(allocator)上调用 alloc(),这意味着你得为你的场景选择最合适的分配器实现。

在 Rust 里,想创建一个可变的全局变量难到能让人写出一长串论坛讨论[3] 来研究该怎么做;而在 Zig 里,你直接创建一个就行,没问题。

在 Zig 中,未定义行为依然是严肃的问题

Zig 把它称为“非法行为”(illegal behavior)。它尝试在运行时检测到这些情况,并在发生时让程序崩溃。

担心这些检查有性能开销的人也不必太担心,Zig 在构建程序时提供了四种不同的“发布模式”(release mode)可选,其中有些模式会关闭这些检查。

它的思路似乎是,你可以在开启检查的模式下把程序跑得足够多次,从而对“关闭检查的构建里不会发生非法行为”获得合理的信心。我觉得这是一种高度务实的设计。

Zig 与另外两门语言的另一个差异,是它与面向对象编程的关系。

OOP 早已不再时髦,Go 和 Rust 都回避了类继承

但 Go 与 Rust 对其他面向对象的惯用法支持得足够多,以至于如果你愿意,仍然可以把程序组织成一张由对象相互交互构成的图。

Zig 有方法(method),却没有私有结构体字段,也没有任何实现运行时多态(也就是动态分发)的语言特性。尽管 std.mem.Allocator 简直 “迫不及待” 想当一个接口。就我所见,这些缺失是有意为之,Zig 是一门为面向数据的设计(data-oriented design)而生的语言[4]。

还有一点我想补充,因为它让我很受启发。

在 2025 年还去打造一门手动内存管理的编程语言,乍看甚至有些疯狂

尤其是在 Rust 已经证明,你甚至不需要垃圾回收,把这事交给编译器也行。但这项选择与“排除 OOP 特性”的选择密切相关。

在 Go、Rust 以及许多其他语言中,你往往会为对象图里的每个对象一次分配一小块内存。于是你的程序里藏着成千上万次 malloc()free()也就意味着成千上万个不同的生命周期

这就是 RAII[5]。

在 Zig 里,手动内存管理看似会带来大量枯燥、易错的记账工作,但那只是因为你执意要把内存分配绑定在那些小对象上。

你完全可以改为,在程序里某些更合理的时点(比如事件循环每次迭代开始时)一次性分配和释放大块内存,用这块内存去承载你要操作的数据。

Zig 鼓励的正是这种做法。

很多人似乎都困惑[6],既然已经有 Rust,为什么还需要 Zig?

差别并不只是 Zig 更简单。我认为更重要的差异在于,Zig 希望你从代码里剔除更多面向对象的思维方式。

Zig 身上有一种好玩、带点“颠覆”的气质。

它像是一门用来砸碎公司里(对象的)等级制度的语言;也是一门献给自大狂与无政府主义者的语言。

我很喜欢它。我也希望它能尽快发布稳定版,尽管 Zig 团队眼下的优先事项似乎是重写他们的所有依赖[7]。也不是没可能:在看到 Zig 1.0 之前,他们先把 Linux 内核也重写一遍。

[1] 让 Rust 困难的不是生命周期: https://www.scattered-thoughts.net/writing/assorted-thoughts-on-zig-and-rust/

[2] 这条 GitHub 评论: https://github.com/rust-lang/rust/issues/68015#issuecomment-835786438

[3] 一长串论坛讨论: https://users.rust-lang.org/t/whats-the-correct-way-of-doing-static-mut-in-2024-rust/120403

[4] 面向数据的设计(data-oriented design): https://www.youtube.com/watch?v=IroPQ150F6c

[5] 这就是 RAII: https://www.youtube.com/watch?v=xt1KNDmOYqA

[6] 似乎都困惑: https://www.reddit.com/r/rust/comments/1333zs1/zig_or_rust/

[7] 重写他们的所有依赖: https://github.com/ziglang/zig/issues/16270

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-02-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 木有枝枝 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Go
  • Rust
  • Zig
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档