<!-- title: 没GC却造出慢GC：Rust管不了图 -->
<!-- domain: programming -->
<!-- video: https://www.youtube.com/watch?v=ftDTHtgPKJo -->

# 没GC却造出慢GC：Rust管不了图

## 导语

`Rust` 这门语言最响亮的招牌是"**没有垃圾回收器（GC），却能保证内存安全**"。这话听起来像是天上掉馅饼——既不用忍受 Go 那种运行期 GC 的停顿，又不用担心 C/C++ 那种 use-after-free 的地狱。可是当你真拿 Rust 去写一个**图、双链表、DOM 树、编译器 AST** 这类天然带环、带反向引用的数据结构时，编译器会逼你把每个节点套进 `Rc<RefCell<T>>`——引用计数加运行期借用检查。**这玩意儿本质上就是一个又慢又差的 GC**。

这就是这部 **《Zig vs Rust: Who's Closer to the Metal?》** 想撕开的那层窗户纸。它把两门"贴近金属"的系统语言摆在一起，核心一句话：**Rust 卖的是"保证"（guarantees），Zig 卖的是"控制"（control）**。而真正反常识的洞察是——Rust 为了在编译期证明每个指针的寿命，在处理图结构时，被迫手搓出一个比真 GC 还拉的内存管理器；Zig 干脆不证明单个指针，而是把一堆对象塞进一个 **arena（内存竞技场）**，用完一次性释放，反而更省、更快、更好懂。

这篇文章我就带你把这套"借阅检查 vs 显式分配器"的底层账算清楚，并告诉你：**什么时候该认怂，别和 Rust 的借阅检查死磕。**

## 视频背景卡片

| 项目 | 内容 |
|------|------|
| 视频标题 | Zig vs Rust: Who's Closer to the Metal? |
| 频道 | 系统编程科普类频道（视频核心框架：control vs. guarantees）|
| 时长 | 长视频正片 + 1 分钟配套 Shorts（`GTM-YSXF94M`）|
| 播放量 | 新片，增长中（以页面实时数据为准）|
| 核心主题 | 内存安全该由编译期证明（Rust）还是运行期管理（Zig）|
| 视频金句 | "Zig and Rust aim at the same problems from different angles: control vs. guarantees." |

> 说明：因 IP 与配额限制，YouTube 页面元数据未能完整抓取，字幕也未取到。下文视频原话以检索到的描述与配套 Shorts 文案为准，其余为基于公开技术资料与基准测试的延伸分析，已逐段标注。

## 核心内容拆解

### 1. "离硬件更近"是个伪命题，真正的分水岭是"不确定性放哪"

【视频观点】视频一上来就拆穿了一个常见误会：Rust 和 Zig 都编译成原生机器码，**两者都"贴近金属"**，比性能参数是打不赢胜负的。真正的分歧在于一个非常哲学的问题——**程序里那些"不确定会不会出错"的地方，谁来兜底？**

【延伸分析】这里有个干净的二分法：

- **Rust：歧义是编译期错误。** 编译器必须用静态分析**证明**不会发生数据竞争、不会 use-after-free；证不出来，直接拒编。
- **Zig：歧义是运行期责任。** 语言给你 `Allocator` 和 `defer` 这些工具，让你**显式**管资源；安全靠 debug 模式下的运行期检查和测试来兜。

一句话：Rust 把负担压在**编译器和开发者的脑子上**，Zig 把负担压在**运行期体检和程序员的纪律上**。这不是谁对谁错，是两套完全不同的"安全预算"分配方式。

### 2. 借阅检查的铁律：别名与可变，二选一

【视频观点】视频点出了 Rust 借阅检查最核心的一条铁律——**Aliasing XOR Mutation（别名与可变，二选一）**：同一块数据，要么同时被多个引用读，要么被一个引用写，**不能既有别名又可变**。

【延伸分析】这条规则对**树形结构**特别顺滑：父节点独占子节点，所有权层层向下，编译器一眼就能算清谁该被释放。问题出在**非树形结构**——只要两个节点互相指、或者一个节点被多处引用又需要修改，铁律就卡死了。所以业界有句玩笑：**"在 safe Rust 里写一个双链表，难到能出本书。"**（这书还真存在，叫 *Learn Rust With Entirely Too Many Linked Lists*。）这不是 Rust 程序员水平不行，是**线性类型系统**（linear type system）天然和非线性数据结构八字不合——学术界称之为"阻抗失配"（impedance mismatch）。

### 3. Rust 的"图结构地狱"：双链表被迫套上 Rc<RefCell>

【视频观点】视频用双链表（doubly linked list）当靶子。在 C/Zig 里，一个节点老老实实存两个指针 `prev` / `next` 就完事了。在 safe Rust 里，这会形成**引用环**：A 拥有 B，B 又指回 A，drop checker 算不清谁先释放，借阅检查又禁止插入时同时持有可变别名。

【延伸分析】社区的"标准答案"是把所有东西包进 `Rc<RefCell<T>>`：

```rust
struct Node<T> {
    value: T,
    next: Option<Rc<RefCell<Node<T>>>>,
    prev: Option<Weak<RefCell<Node<T>>>>,
}
```

这行代码看着人畜无害，**代价却藏得很深**：

- **内存开销**：每个 `Rc` 要带强引用计数 + 弱引用计数，`RefCell` 要带一个借用状态标志，`Option` 还要一个判别位。**一个节点平白多出约 48 字节**的元数据。
- **运行期开销**：每次访问数据，`RefCell` 都要做一次运行期检查，确认没有别的借用。
- **人体工学**：代码里到处 `.borrow_mut().unwrap()`、`.upgrade()`，啰嗦到想哭。

### 4. 真正的反转：Rc<RefCell> 本质是一个又慢又差的 GC

【延伸分析】这是整部视频（以及我最想强调）的**核心反常识**。`Rc` 是什么？**引用计数（reference counting）**，这就是 GC 学教科书里最古老的一种垃圾回收算法。`RefCell` 的运行期检查，相当于在每次访问时做一次"写屏障"式的校验。

把它们合起来看：**Rust 号称没有 GC，却在处理图结构时，被迫手搓出了一个慢速 GC。** 而且这个"土制 GC"还**两头不靠**——它没有真 GC 的并发屏障和分代优化，却背上了手动管理的全部负担（引用计数的原子操作开销、循环引用导致 `Rc` 还得配 `Weak` 避免内存泄漏）。

业界有一句狠话：**`Rc<RefCell>` 模型本质上是一个慢速的、临时的垃圾回收器，它既没拿到真 GC 的好处，又吃满了手动管理的所有惩罚。** 所以"Rust 没 GC 所以更快"这个直觉，在图密集场景下是**站不住脚的**。

### 5. Zig 的破局：arena 把寿命的不变量"上提"到边界

【视频观点】Zig 不走证明单个指针的路子。它的做法是**区域式内存管理（region-based memory management）**：标准库里**没有全局 allocator**，每个数据结构都要求你显式传一个 `Allocator` 参数进来。

【延伸分析】Zig 的双链表节点定义，朴素到像在写 C：

```zig
const Node = struct {
    value: i32,
    next: ?*Node,
    prev: ?*Node,
};
```

魔法在管理方式——把一堆节点塞进一个 **`ArenaAllocator`**：

```zig
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var node1 = try allocator.create(Node);
var node2 = try allocator.create(Node);
node1.next = node2;   // 循环引用? 完全没问题
node2.prev = node1;
// 作用域结束时, arena.deinit() 一把全释放
```

**这一招才是精髓。** 你**根本不需要**证明 node1 比 node2 长寿、不需要证明谁先死；你**只需要**保证一件事——**arena 活过这次操作**。换言之，Zig 把"寿命证明"这个不变量，从**每个对象**层面，**上提**到了**一个区域边界**层面。证明一个边界，远比证明成千上万个指针容易得多。

性能账也好算：

| 指标 | Rust（Rc<RefCell>）| Zig（ArenaAllocator）|
|------|------|------|
| 每节点内存开销 | ~48 字节 | ~0 字节 |
| 分配策略 | 逐个 malloc | 连续 bump pointer（O(1)）|
| 缓存局部性 | 散落、碎片化 | 高（空间局部性好）|
| 释放代价 | O(N) 引用计数递减 | O(1) 整片释放 |

对编译器、路由引擎、DOM 操作这类**图为中心**的场景，Zig 的模型不仅更好写，还**严格更快**——遍历速度能到约 3 倍，因为数据在内存里是连续排布的，CPU 缓存命中率高得离谱。

### 6. 安全的两种哲学：编译期证明 vs 运行期体检

【视频观点】有人会反驳：Rust 给的是**数学证明级**的保证，Zig 给的只是"差不多就行"。视频承认这一点——Zig 确实没有 Rust 那种"编译通过就一定没有内存错误"的硬保证。

【延伸分析】但 Zig 不是裸奔。它的 `GeneralPurposeAllocator`（GPA）在 debug 模式下会主动**检测泄漏和 use-after-free**：

```zig
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
    const check = gpa.deinit();
    if (check == .leak) @panic("Memory Leak Detected!");
}
```

再配合 `ReleaseSafe` 模式（生产构建也能保留检查，只损失极小性能）、**默认检查的整数溢出**、**切片越界检查**，Zig 实务上能在开发阶段抓住大约 **99%** 的内存错误。**没有数学保证，但有工程上够用的反馈环。** 很多系统工程师的判断是：为了那最后 1% 的理论保证，付出现实中图结构地狱的代价，不值。这是一个"**严格性 vs 工程性价比**"的经典权衡。

### 7. 并发的"着色问题"：Rust 的 async 有色，Zig 无色

【视频观点】视频第二个支柱是并发。Rust 的 async/await 是**"有色函数"**（colored），Zig 的并发是**"无色"**（colorless）。

【延伸分析】这里的术语来自 Bob Nystrom 2015 年那篇著名文章 *《What Color Is Your Function?》*。核心吐槽是：一旦你引入 `async`，函数就被**染上了第二种颜色**，于是整个调用栈都被污染——同步函数不能直接调用异步函数（得 `await`），异步函数又会编译成状态机，于是冒出一堆 `Pin`、`Unpin`、`Send`、`Box::pin` 这种把新手劝退的概念。Rust 里**异步递归**至今是个痛点，因为编译期必须知道状态机大小，递归 async 只能手动 `Box::pin`。

Zig 走的是另一条路：函数**没有 `async` 关键字**，也没有传统意义上的 `await`。一次 `reader.read()` 可能真的阻塞，也可能悄悄挂起一个 green thread——**代码长得一模一样**。这叫"**Green Threads on io_uring**"。结果就是一个解析文件的通用函数，能不改一行、不重新编译，同时用在阻塞的命令行工具和高并发的服务器里。**"async Rust"和"sync Rust"几乎被诟病成两门语言，而 Zig 只有一门。**

### 8. 选型：保票（Guarantee）vs 控制（Control）

【视频观点】收尾部分视频把结论收得很干净：**2026 年的系统编程，本质是在"保票"和"控制"之间做选择。**

【延伸分析】这其实是个**决策**，不是个"谁更强"的站队：

- **选 Rust 的"保票"**：代码能编过，就保证没有数据竞争、没有内存腐败。对**网络服务、加密、Linux 内核**这种 segfault 代价惨烈的场景，Rust 依然称王，而且生态无敌——"如果一个库存在，那它大概率在 Rust 里"。2025 年起 Rust 已正式进入 Linux 内核。
- **选 Zig 的"控制"**：内存布局、分配模式、I/O 调度，**全部由你说了算**。对**启动时间敏感的 CLI、二进制大小敏感的嵌入式、图为中心的数据库内核**，Zig 占优。代表选手是 **Bun**（JS 运行时）和 **TigerBeetle**（金融数据库），它们就是用 Zig 把每个 CPU 周期榨干。

还有个常被忽略的维度：**编译速度**。Zig 的自托管编译器在 debug 构建时**绕过 LLVM 直接吐机器码**，改一个函数体，增量编译只要 **0.05 秒**，而 Rust 要 **2.5 秒**；干净构建 Zig 约 4 秒，Rust 约 45 秒。对一个反复迭代渲染循环、物理引擎的人来说，这种"**瞬时的反馈回路**"本身就是生产力乘数。

## 知识结构图

```mermaid
flowchart TD
    Q["需求: 系统级编程 + 内存安全"]
    Q --> Split{"不确定性放哪兜底?"}
    Split -->|"编译期证明"| R["Rust: 借阅检查 Borrow Checker"]
    Split -->|"运行期管理"| Z["Zig: 显式分配器 Allocator"]

    R --> R1["铁律: 别名 XOR 可变"]
    R1 --> R2["树形结构: 顺滑"]
    R1 --> R3["图/循环结构: 卡死"]
    R3 --> R4["被迫套 Rc RefCell<br/>≈ 又慢又差的 GC<br/>每节点 +48B"]

    Z --> Z1["每个结构带 Allocator 参数"]
    Z1 --> Z2["Arena: 一次 init/deinit"]
    Z2 --> Z3["不必证每指针寿命<br/>只证 arena 活过操作"]
    Z --> Z4["Debug GPA + ReleaseSafe<br/>抓住 ~99% 内存错误"]

    R4 --> C{"选型决策"}
    Z3 --> C
    C -->|"segfault代价极高 / 大团队 / 网络"| PickR["→ Rust: 要保票"]
    C -->|"启动快 / 二进制小 / 嵌入式 / 图为中心"| PickZ["→ Zig: 要控制"]
```

## 实战启示

**第一，别在图结构上和借阅检查死磕。** 如果你写的是编译器 AST、DOM、路由表、游戏场景图、社交关系网这类**带环或共享引用**的数据，认真评估两个选项：要么上 Zig，要么在 Rust 里用 **arena（`typed-arena`、`bumpalo`）** 模仿区域式内存，而不是无脑 `Rc<RefCell>`。后者会悄悄把你"没 GC"的项目变成一个带慢速 GC 的项目。

**第二，"没 GC"不等于"没内存管理成本"。** Rust 把成本转移到了编译期和你的心智上；Zig 把成本留在了运行期检查和纪律上。选语言时算的是**总账**，不是单看某个卖点。

**第三，给团队画一棵决策树。** segfault 成本极高、团队大、要并发安全 → Rust；启动时间、二进制大小、嵌入式、以图为中心的内核 → Zig；要快速迭代、要"改一行秒级反馈"→ Zig 的编译速度是真优势。

**第四，着色问题值得纳入技术评审。** 一旦一个项目的调用栈大部分染上 async 颜色，`Pin`/`Send`/`Box` 的复杂度会指数级上升。在 Rust 项目里，能用同步库就别轻易全栈 async；或者考虑把 I/O 密集部分隔离。

**第五，工具崇拜要警惕。** 2026 年的趋势页（developereducators）上"AI Coding / vibe coding"频道增速最猛，但真正决定系统质量的，依旧是这类**内存模型与并发模型的根本选择**，而不是用哪个 AI 写代码。

## 延伸学习路径

1. **Bob Nystrom《What Color Is Your Function?》（2015）** — "函数着色"概念的源头，理解 async/await 为何"有毒"的必读。
2. **《Learn Rust With Entirely Too Many Linked Lists》** — 用四种姿势在 safe Rust 里写双链表，活生生演示借阅检查的痛。
3. **Zig 官方文档的 Allocators 章节** — 读懂 `ArenaAllocator`、`GeneralPurposeAllocator`、`page_allocator` 的分工。
4. **TigerBeetle 设计文档《Designing for Determinism》/ Bun 技术博客** — 看"ZigFat"级别的工业项目如何榨干每个 cycle。
5. **Rust 的 `typed-arena` / `bumpalo` crate 文档** — 在 Rust 里复刻区域式内存、缓解图结构地狱的实战方案。

## 一句话总结

**Rust 在编译期逐个证明指针寿命，结果在图结构里被迫手搓出一个慢速 GC；Zig 不证单个指针，只证一个 arena 边界，反而把安全、性能和可读性一次性拿回——所以"没 GC"从来不是终点，"把寿命证明放到哪一层"才是真正的设计战场。**

## 参考来源

1. [Zig vs Rust: Who's Closer to the Metal?](https://www.youtube.com/watch?v=ftDTHtgPKJo) — 本文分析的核心视频
2. [Rust vs Zig in 2026: Why Systems Engineers Are Choosing Manual Memory Management](https://www.compiler.today/systems-programming/rust-vs-zig-memory-management-concurrency-2026) — 借阅检查 vs 分配器的深度技术对照（性能基准、代码示例主要来源）
3. [Colored vs uncolored functions — Programming Language Design StackExchange](https://langdev.stackexchange.com/questions/3430/colored-vs-uncolored-functions) — 函数着色问题的学术讨论
4. [Why Zig Is Winning Hearts Where Rust's Borrow Checker Struggles — Medium](https://medium.com/@anshusinghal703/why-zig-is-winning-hearts-where-rusts-borrow-checker-struggles-a-candid-comparison-db78cf643467) — 社区视角的补充观点
5. [Rust vs. Zig: The New Programming Language Battle for Performance — dev.to](https://dev.to/mukhilpadmanabhan/rust-vs-zig-the-new-programming-language-battle-for-performance-1p6) — 嵌入式与裸金属场景的对比

---
> 本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵**爱卫生**（微信ID: gprshome201101）维护，聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。
> 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
