2026 年的 Zig 与 Rust
摘要
本文在 2026 年的背景下对比了 Zig 和 Rust,认为编程代理通过自动化生成 Rust 代码,削弱了 Zig 在人机交互体验上的优势。
<p><a href="https://lobste.rs/s/fnhsha/zig_vs_rust_2026">评论</a></p>
查看缓存全文
缓存时间: 2026/05/12 01:12
# 2026 年 Zig 与 Rust 的较量
来源:https://zackoverflow.dev/writing/zig-vs-rust-in-2026 2026 年 5 月 11 日 ---
在近 3 年前,在编程代理(coding agents)出现之前,我用 Zig 和不安全的 Rust 编写了一个字节码虚拟机和垃圾回收器(https://zackoverflow.dev/writing/unsafe-rust-vs-zig),当时我觉得在编写不安全代码方面,Zig 的人体工学优势更大。不幸的是,如今我手动编写的代码越来越少,这意味着我现在使用 Zig 的理由也越来越少。Zig 特性带来的 1.5 到 5 倍的人为开发体验(DX)生产力提升,完全被 Rust 中编程代理带来的 100 倍生产力提升所掩盖(https://zackoverflow.dev/writing/rust-is-the-best-language-for-coding-agents)。Zig 的许多最出色的特性都是为了人类的人体工学而设计的,但这对于代理来说并不那么重要。我将谈谈我最喜欢的 Zig 特性,以及为什么它们现在不再那么重要了。
## 分配器接口 (Allocator Interface)
(https://zackoverflow.dev/writing/zig-vs-rust-in-2026#allocator-interface)
这是我最喜欢的 Zig 特性:使用专用的分配器来优化代码路径(例如竞技场分配、栈回退等),感觉简直像是“银河系大脑”(galaxy brain,意指极其聪明或高级的做法)。
```zig
// 一个真实示例:
// 读取用户输入行需要堆分配器,因为
// 理论上输入长度是无界的。但在实践中,
// 输入几乎总是很短的搜索查询或路径,
// 远小于 1kb。
//
// stackFallback 在栈上放置一个固定大小的缓冲区,
// 只有当输入溢出时才访问堆。常见情况
// 的成本为零堆分配。罕见的长输入仍然有效,
// 因为分配器会静默地升级到堆。
var stack_fallback = std.heap.stackFallback(256, heap_allocator);
const alloc = stack_fallback.get();
const line = try reader.readUntilDelimiterAlloc(alloc, '\n', 4096);
defer alloc.free(line);
```
过去在 Rust 中的问题是,没有等效的分配器接口,如果你想让 `Vec` 使用自定义分配器,你实际上不得不复制粘贴标准库版本并进行修改(这正是 Bumpalo 所做的,看看它的源码 (https://github.com/fitzgen/bumpalo/tree/main/src/collections),集合都是标准库版本的分支,并连接到 bump 分配器)。很长一段时间以来,nightly 版本中已经有了 `Allocator` trait,而且现在看来效果不错。因为它是一个 trait,所以它是静态分发,而 Zig 的是基于虚函数表(vtable)的。与 Zig 不同,Rust 社区没有一种广泛遵循的设计约定,即让数据结构基于分配器进行参数化,但 AI 改变了游戏规则,使得复制粘贴代码并修改这一点变得微不足道。我发现这对于我的用例来说已经足够好了。
## 任意位宽整数 + 打包结构体 (Arbitrary bit width integers + packed structs)
(https://zackoverflow.dev/writing/zig-vs-rust-in-2026#arbitrary-bit-width-integers--packed-structs)
这是另一个我钟爱的 Zig 特性。它使得进行 DOD(数据导向设计)风格的 CPU 缓存优化以及诸如标签指针、NaN boxing 等操作变得非常容易,甚至让位标志(bitflags)的制作也变得极其简单。这里有一个真实示例。当通过 Obj-C 运行时 C API (https://developer.apple.com/documentation/objectivec/objective-c-runtime?language=objc) 使用 Obj-C API(如 Metal)时,`id` 可以是一个标签指针,而不是对齐的堆对象指针。我通过代码传递了一个标签化的 `NSNumber`,而该代码假设了对齐方式,从而触发了未定义行为(UB)。因此,你需要一种廉价的“堆指针与标签立即数”检查方法。以下是简化的 Objective-C 标签指针布局:低位的一位表示“不是堆指针”,接下来的 3 位标识类槽,剩余的 60 位是有效载荷:
```zig
pub const TaggedClass = enum(u3) {
ns_atom = 0,
ns_string = 1,
ns_number = 2,
ns_date = 3,
};
pub const ObjcTaggedPointer = packed struct {
is_tagged: bool = true,
class: TaggedClass,
payload: u60,
pub fn ns_number(n: u60) ObjcTaggedPointer {
return .{ .class = .ns_number, .payload = n };
}
pub fn from_raw(raw: u64) ObjcTaggedPointer {
return @bitCast(raw);
}
pub fn raw(self: ObjcTaggedPointer) u64 {
return @bitCast(self);
}
pub fn is_ns_number(self: ObjcTaggedPointer) bool {
return self.is_tagged and self.class == .ns_number;
}
};
```
Rust 的等效做法是在构建时对 Objective-C 类槽进行 OR 运算,并在每次访问时将其掩码出去。该槽只是一个 `u64` 常量,而不是真正的类型:
```rust
pub struct ObjcTaggedPointer(u64);
impl ObjcTaggedPointer {
const TAG_MASK: u64 = 0b1;
const CLASS_MASK: u64 = 0b1110;
const CLASS_SHIFT: u64 = 1;
const PAYLOAD_SHIFT: u64 = 4;
const CLASS_NS_ATOM: u64 = 0;
const CLASS_NS_STRING: u64 = 1;
const CLASS_NS_NUMBER: u64 = 2;
const CLASS_NS_DATE: u64 = 3;
pub fn ns_number(n: u64) -> Self {
Self(
(n << Self::PAYLOAD_SHIFT)
| (Self::CLASS_NS_NUMBER << Self::CLASS_SHIFT)
| Self::TAG_MASK,
)
}
pub fn is_ns_number(self) -> bool {
self.0 & Self::TAG_MASK != 0
&& (self.0 & Self::CLASS_MASK) >> Self::CLASS_SHIFT == Self::CLASS_NS_NUMBER
}
}
```
你可以看到,原生的 Rust 方式很不具人体工学。你最好使用一些像 bitfield (https://docs.rs/bitfield/latest/bitfield/) 或 bitflags (https://crates.io/crates/bitflags) 这样的 crate,它们都依赖于过程宏魔法才能工作,我不觉得这像 Zig 的打包结构体那样优雅。然而,有了编程代理,我根本不在乎手动编写代码有多么麻烦。
## 编译时执行 (Comptime)
(https://zackoverflow.dev/writing/zig-vs-rust-in-2026#comptime)
这是 Zig 最引人注目的特性,除了某些晦涩的依赖类型语言外,没有其他编程语言拥有像 Zig 这样出色的编译时评估功能。我以为我会非常想念它,但实际上并没有。对我来说,95% 的 comptime 用法是为了创建具有参数化类型的 Zig 版泛型数据结构,例如:
```zig
fn ArrayList(comptime T: type) type {
return struct {
items: []T,
capacity: usize,
allocator: Allocator,
};
}
const IntList = ArrayList(i32);
```
恕我直言,Rust 拥有更好的类型系统设计(见下一节)。在剩下的 5% 的情况下,没有 comptime 确实很糟糕。达到等效效果的唯一可靠方法是通过代码生成。我现在正在制作一款游戏,我有从工具生成的硬编码碰撞体几何数据,我想将其烘焙到数据结构中。如果没有 comptime,我必须让 Claude 编写一个脚本来生成 Rust 文件。不过,我实际上也不需要那么多次编译时评估。
## Rust 的类型系统
(https://zackoverflow.dev/writing/zig-vs-rust-in-2026#rusts-type-system)
我想我宁愿用 comptime 交换 Rust 设计更好的类型系统,尤其是对于有界多态性(traits/类型类)。尝试在 Zig 中实现等效功能简直是噩梦。此外,我认为 Rust 的类型系统允许你强制执行更多的不变量,并防止编程代理犯常见的错误。在我的游戏中,我使用 euclid (https://docs.rs/euclid/latest/euclid/) crate,它基本上允许你不混淆坐标空间(图形编程中非常常见的问题),即为每个坐标空间创建专门化类型(例如 `Point` 或 `Point`):
```rust
use euclid::{point2, vec2, Point2D, Translation2D, Vector2D};
struct WorldSpace;
struct ScreenSpace;
type WorldPoint = Point2D<WorldSpace, f32>;
type WorldVector = Vector2D<WorldSpace, f32>;
type ScreenPoint = Point2D<ScreenSpace, f32>;
fn main() {
let player: WorldPoint = point2(10.0, 20.0);
let movement: WorldVector = vec2(5.0, 0.0);
// 允许:同一空间中的点 + 向量。
let moved_player = player + movement;
// 允许:显式从世界空间转换到屏幕空间。
let world_to_screen = Translation2D::<WorldSpace, ScreenSpace, f32>::new(100.0, 50.0);
let player_on_screen: ScreenPoint = world_to_screen.transform_point(moved_player);
// 不允许:不能混合坐标空间。
let bad: ScreenPoint = player;
}
```
这可以防止代理犯下将世界空间坐标与屏幕空间坐标混淆的愚蠢错误:euclid crate 防止坐标空间混淆
## 无需处理内存问题
(https://zackoverflow.dev/writing/zig-vs-rust-in-2026#not-having-to-deal-with-memory-issues)
随着编程代理允许编写多 100 倍的代码,这也意味着你需要仔细检查多 100 倍的 Zig 代码以查找内存问题。在没有形式化验证的情况下,枚举以查找错误的搜索空间表面积现在变得如此之大。鉴于现在生成的代码量级,Rust 显得更具吸引力。Rust 的权衡一直是它会阻碍开发人员的生产力,尤其是如果你不熟悉借用检查器的话,但这在编程代理面前已经无关紧要了。如果你确实在 Rust 中使用了 `unsafe`,还有像 miri (https://github.com/rust-lang/miri) 这样的工具,你可以让编程代理运行代码对抗这些工具,以确保它不会导致未定义行为(UB)或在涉及 `unsafe` 时违反 Rust 的别名规则。
## 结语
(https://zackoverflow.dev/writing/zig-vs-rust-in-2026#closing)
我仍然怀念编写 Zig,并认为它是一种很棒的编程语言,但我更喜欢 Rust,而且编程代理在 Rust 中运行得更好。
相似文章
Zig 项目坚持严格反 AI 贡献政策的理由
本文探讨了 Zig 项目对 AI 生成内容的严格禁令,引用了 Loris Cro 提出的“贡献者扑克”理念,该理念强调培养人类贡献者优先于处理代码数量。文章还阐述了这一政策如何影响使用 Zig AI 辅助分支的 Bun 运行时。
@seclink: Bun 是一款于 2022 年发布、旨在替代 Node.js 的高速 JavaScript 运行时,尽管性能表现强劲,但其基于 Zig 语言编写的代码库却面临着稳定性方面的挑战。 Sumner 利用 Anthropic 公司的 Claud…
Sumner 使用 Anthropic 的 Claude AI 将 Bun 运行时的 96 万行 Zig 代码成功移植到 Rust,验证了 AI 在代码重写方面的巨大效率潜力。
如果 AI 为你写代码,为何还要用 Python?
文章认为,AI 在 Rust 和 Go 等复杂系统级编程语言方面的熟练程度,改变了 Python 的价值主张,因为 AI 降低了进入高性能开发领域的门槛。
用 Zig 写一个 C 编译器
一位开发者记录了用 Zig 语言、按照 Nora Sandler 的教程系列构建名为 paella 的 C 编译器的全过程。
Kuri – 基于 Zig 的 agent-browser 替代方案
Kuri 是一款面向 AI 智能体的 Zig 浏览器自动化工具包,仅 464 KB 二进制,冷启动约 3 ms,每个工作流循环的 token 使用量比 agent-browser 低 16%。