|
| 1 | +--- |
| 2 | +.title = "从 Rust 到 Zig,我写了一个小型 KV 数据库后,终于理解 Zig 到底“怪”在哪", |
| 3 | +.date = @date("2026-04-02T13:11:11+0800"), |
| 4 | +.author = "lispking", |
| 5 | +.layout = "post.shtml", |
| 6 | +.draft = false, |
| 7 | +--- |
| 8 | + |
| 9 | +> 作为一个 Rust 爱好者,在真正深入使用 Zig 之后,我对这门语言的理解发生了转变。 |
| 10 | + |
| 11 | +在这篇文章中,我将通过 vibe coding 写的一个 **小型 KV 数据库** 项目来分析 Zig 和 Rust 的异同。 |
| 12 | + |
| 13 | +[Rust vs Zig]($image.siteAsset('images/rust-vs-zig.webp')) |
| 14 | + |
| 15 | +## `kvdb`:一款轻量级嵌入式 KV 数据库 |
| 16 | + |
| 17 | +首先,来看看我们这个项目:`kvdb`。它是一个轻量级、嵌入式的 **Key-Value 数据库**,采用了 **B-tree 索引** 和 **写前日志(WAL)** 来确保数据持久化。核心功能如下: |
| 18 | + |
| 19 | +* **B-tree** 结构:实现高效的键值对插入、查找和删除。 |
| 20 | +* **写前日志(WAL)**:确保数据在发生崩溃时的可恢复性。 |
| 21 | +* **事务支持**:提供基本的事务机制,保证操作的原子性。 |
| 22 | +* **页面存储**:使用固定大小的 4KB 页进行存储,以优化磁盘 I/O 性能。 |
| 23 | + |
| 24 | +然而,尽管这个项目具备了数据库的基本功能,它更像是一个数据库内核的雏形,并不完全是一个成熟的产品。 |
| 25 | + |
| 26 | +> 仓库地址:https://github.com/lispking/kvdb |
| 27 | + |
| 28 | +## 设计与实现:从 Rust 的眼光看 Zig |
| 29 | + |
| 30 | +在实现过程中,我很快就意识到,Zig 和 Rust 在设计哲学上有显著的差异。 |
| 31 | + |
| 32 | +* Rust 是一门注重“内存安全”的语言,依靠 **ownership** 系统来确保内存安全,无需垃圾回收。 |
| 33 | +* 而 Zig 则倾向于“暴露真相”,减少语言的魔法,给程序员更多的控制权。 |
| 34 | + |
| 35 | +### **Zig 的优点:暴露底层,简洁透明** |
| 36 | + |
| 37 | +Zig 在内存管理、控制流等方面非常直接: |
| 38 | + |
| 39 | +* 没有隐藏的控制流或内存分配,所有操作都是显式的。 |
| 40 | +* 没有预处理器,也没有宏。它的语法看起来可能有点古怪,但这正是 Zig 希望消除复杂抽象的体现。 |
| 41 | + |
| 42 | +这种设计让我在写 `kvdb` 时感受到了极大的灵活性,虽然刚开始时有些不适应。 |
| 43 | + |
| 44 | +### **Rust 的优点:内存安全与高抽象** |
| 45 | + |
| 46 | +与此相对,Rust 则通过 **ownership**、**borrowing** 和 **lifetime** 等特性提供了强大的内存安全保证,它通过 **编译期检查** 让程序员避免了许多常见的内存错误。 |
| 47 | + |
| 48 | +Rust 会在编译时就要求程序员“遵守规则”,这让我在写代码时不需要担心内存泄漏或者悬空指针等问题。在大型工程中,Rust 的这一点尤其重要。 |
| 49 | + |
| 50 | +### **Zig 让我“重新认识”底层控制感** |
| 51 | + |
| 52 | +Zig 的核心哲学是“程序员直接面对控制流和内存管理”。 |
| 53 | + |
| 54 | +在我写 `kvdb` 时,我不得不深入思考每一行代码,思考它如何与硬盘交互、如何管理内存、如何确保数据一致性。 |
| 55 | + |
| 56 | +Zig 不会通过隐藏一些复杂的实现来“保护”我,而是让我在做每个决定时都能清楚了解背后的原因。 |
| 57 | + |
| 58 | +这种直接控制的感觉,非常像我之前在 C 语言中体验到的那种“强烈的底层感”。不过,Zig 的优势在于,它减少了 C 语言中的许多危险操作,同时仍保留了较高的控制力。 |
| 59 | + |
| 60 | +## `kvdb`:当前的局限性与改进方向 |
| 61 | + |
| 62 | +尽管 `kvdb` 的架构已经具备了基本的数据库功能,但它并不完美。当前最显著的问题是 **B-tree 索引** 部分: |
| 63 | + |
| 64 | +1. **B-tree 目前是单页版本**:`BTree.put()` 方法会检查 root 是否已满,如果已满就返回 `Error.NodeFull`,而没有实现分裂节点的逻辑。这样一来,B-tree 的操作非常简化,无法支持真正的多页树结构。 |
| 65 | +2. **事务回滚不完全**:`abort()` 方法并没有完全实现 WAL 的回滚机制,而是通过 `reload()` 恢复状态。虽然 WAL 已经在代码中实现,但恢复操作的精细度还远远不够。 |
| 66 | +3. **WAL 的性能问题**:每次写入 WAL 后都会执行 `sync()`,这对于性能要求较高的场景来说会成为瓶颈。 |
| 67 | + |
| 68 | +### **B-tree 需要支持节点分裂与多页操作** |
| 69 | + |
| 70 | +目前,B-tree 只支持单页插入,当根节点满时直接返回 `Error.NodeFull`,缺少分裂、合并和多页遍历的完整实现。接下来,应该完善分裂和合并机制,确保数据库能够处理更大规模的数据集。 |
| 71 | + |
| 72 | +### **事务回滚与恢复机制的改进** |
| 73 | + |
| 74 | +`abort()` 方法虽然已经实现了基本的回滚,但还缺少通过 WAL 重放来实现更细粒度的撤销。后续可以进一步增强这一部分,使得系统能够在发生故障时,准确地恢复到正确的状态。 |
| 75 | + |
| 76 | +### **WAL 的性能优化** |
| 77 | + |
| 78 | +目前,WAL 的每条记录都进行 `sync()`,这对于写入密集型应用来说可能会影响性能。未来可以通过引入批量提交、事务提交时才刷新 WAL 等方式来优化这一部分。 |
| 79 | + |
| 80 | +## 总结 |
| 81 | + |
| 82 | +> Zig 的独特魅力与 Rust 的优势 |
| 83 | + |
| 84 | +通过这次使用 Zig 写数据库内核的经历,我更加深刻地理解了 **Rust 和 Zig 的设计哲学**。 |
| 85 | + |
| 86 | +* Rust 强调编译时安全,强调内存和并发安全。 |
| 87 | +* 而 Zig 则通过显式的控制流和内存管理,给程序员更多的自由和控制权。 |
| 88 | + |
| 89 | +两者各有千秋。 |
| 90 | + |
| 91 | +在项目中,我对 Zig 的控制力和透明性有了全新的理解。尽管它的语法有点“古怪”,但它的设计哲学却让我对底层编程有了更深的体验。 |
| 92 | + |
| 93 | +### **Rust**: |
| 94 | + |
| 95 | +* 强调安全、性能和并发。 |
| 96 | +* 适用于长期维护、大型项目。 |
| 97 | + |
| 98 | +### **Zig**: |
| 99 | + |
| 100 | +* 更加透明、直接控制底层。 |
| 101 | +* 适合高性能计算、嵌入式应用。 |
| 102 | + |
| 103 | +如果让我选择,我会依然选择 Rust 作为主要开发语言,尤其是在大型系统和复杂并发场景下。 |
| 104 | + |
| 105 | +但 Zig 的底层控制感和灵活性,绝对值得在一些特定场景下使用。 |
0 commit comments