从 Rust 转 Zig:手动内存管理的痛
What Zig felt like, coming from Rust
作为一名深耕 Rust 七年的开发者,我最近尝试用 Zig 重写了 JSONPath 项目。这次经历让我惊讶地发现,Zig 的 IDE 支持几乎为零,迫使我回归命令行工作流,反而让我重新审视了工具链的本质。在代码结构上,Zig 倾向于扁平化,这与 Rust 习惯的深层目录结构形成鲜明对比。最核心的冲击来自编程范式:Rust 推崇的函数式编程在 Zig 中难以施展,手动管理分配器(Allocator)和显式的内存释放让代码充满了琐碎的样板代码。虽然 Zig 避免了 C 语言的静默崩溃,但那种需要时刻警惕内存泄漏的紧张感,对于习惯了 Rust 自动垃圾回收机制的我来说,无疑是一次巨大的挑战。
这看起来在纸面上显而易见,直到代码变得复杂,这些 bug 才会纠缠在一起并隐藏起来。
HN 评论区
316- Syzygies
出于各种目的,我参与了一个语言对比项目,其中包含 C 语言及其候选后继者,如 Go 和 Zig。其中一个问题是为一个用 32 位 K&R C 编写的 1980 年代计算机代数系统选择归档移植的目标语言。虽然 Zig 是一个出色的调试编译器,但它目前还不足以稳定到成为归档用途的最佳目标语言。
https://github.com/Syzygies/Compare
这就好比你身处一家不会说当地语言的餐厅,看不懂菜单,但看到三道包含招牌菜的套餐,价格各不相同。(比如云南的“过桥米线”。)你会选哪道?我的导游、作者 Fuchsia Dunlop 后来也同意我的观点:这显而易见,选中间价位的那道。
所以,当你需要在 Go 和 C23 之间选择作为 K&R C 的候选后继者时,它们都拥有“皇室血统”。一个继承了名字。除此之外一无所知,你会选哪个?
答案同样显而易见。继承了名字的那个,也继承了那些毛病。
- csense
其中一个章节标题写着“可变性与不可变 Monad 是核心区别”,但这并不正确。
该部分的代码目的和大致轮廓非常清晰:“给我一个函数和数据;如果数据是裸数据,就对其应用该函数;如果数据是容器,就对容器内的每个元素应用该函数。”
你完全可以在 Zig 中使用不可变数据结构来实现这一点。你只需向函数传递一个分配器(即,不是调用 data.flat_map(f),而是调用 data.flat_map(a, f),其中 a 是你的分配器)。
Zig 版本的代码之所以进行可变操作,是程序员的个人选择,而非语言强制要求的。
(另外,Monad 跟这事儿有什么关系?)
- weinzierl
补充两点:
1. 工具链(作为对前述 IDE 支持点的延伸)。Zig 和 Rust 都因其工具链而备受赞誉,我认为这是实至名归。Zig 在 C/C++ 互操作和交叉编译方面的表现非常出色。但从实际从业者的角度来看,我认为 Rust 遥遥领先。考虑到 Zig 年轻得多,这并不令人意外,但值得注意。
2. 编译时特性:这里 Zig 备受赞誉,而 Rust 则不然。我认为这是不公正的。Rust 对其编译时特性的期望要高得多,即无论代码何时运行,结果必须完全一致。这是一个非常有用的特性,但也让任务变得困难得多,从根本上使其与 Zig 的 comptime 无法相提并论。
- plqbfbv
也许我还没那么深入编程领域,但我不太理解 Zig 为何如此火爆?
我写过一点 Rust,算不上专家,但在我看来,Rust 在编译时基本解决了内存管理问题,且无需垃圾回收器(GC),效果非常好。我迄今为止反复看到的最大缺点和代价就是“编译速度慢”,我理解这一点,如果你超过了 250 个 crate,最终的 --release 链接确实会变得明显,但增量编译已经有了改进。
另一方面——看这篇帖子里的语法——Zig 感觉像是 JavaScript、Python 和 Golang 语法的混合体,却仍然需要手动内存管理。所以这就是一种写得更好看的 C,却继承了 C 的所有问题?正如帖子所说:没有函数式编程、数据可变、内存泄漏、双重释放、内存损坏。
个人而言,如果这是另一个选项,我宁愿每次最终链接多花几分钟。
- spider-mario
“首先让我感到措手不及——老实说,谁会料到这是最令人难忘的部分——是 IDE 支持,或者说是几乎完全缺乏支持。”
我完全预料到了这一点。
- tialaramex
我认为当我们回顾 2020 年代时,会被对分配器(Allocator)的痴迷所震撼。
所有手工编写的“C 后继者”语言似乎都有这种痴迷,不仅包括 Zig,还有 Odin、C3 和 Jai。
对于某些玩具级的问题,你可以运用巧妙的分配器技巧来获得巨大的性能提升。例如,Jai 和 Odin 似乎都非常希望你编写代码,以便定期丢弃“每帧”(per-frame)的 arena,这样它们就不必为跟踪 arena 中的分配而付费,因为它们会同时被全部丢弃。
但很多现实世界的软件并没有那么简单。这并不意味着这些功能毫无价值,只是说明它们只是经验丰富的开发者工具箱中成千上万种工具之一,并不真正值得登上头条。
- jauco
既然作者提到了使用 helix,且 Zig 鼓励编写包含更多内容的大型文件,我很好奇他们如何在 helix 中导航这些文件。让我无法使用 helix 的原因是缺乏代码折叠功能,我发现在导航大型文件时经常需要用它来“缩放”查看。
- ozgrakkurt
建议学习如何使用 arena 分配。你会遇到类似这样的模式:
fn run_query(alloc) {
arena = init_arena(alloc);
defer arena.deinit();
}