Zig、Rust 与其他语言
Zig 的分配模型与“没有隐藏分配”
- 多条评论质疑文章中关于 Zig 避免隐藏分配的强烈说法。
- 标准 C 函数大多不会分配;
strdup是一个明显例外。 - 在 Zig 中,大多数 stdlib API 遵循“如果这可能分配,就传入 allocator”的惯例,但并非全部如此:
std.Thread.spawn会分配(线程栈 + 参数负载的堆内存),但没有显式的 allocator。std.ArrayList在内部保存 allocator,因此即使没有传入 allocator,方法调用也可能分配。ArrayListUnmanaged的存在是为了把 allocator 保持在更高层级,并让分配显式化。
- 一些人认为这“只是一种惯例”,而不是严格保证;另一些人则认为这依然很有价值。
不同语言之间的内存管理权衡
- Nim 被强调为一种内存管理可配置的系统语言;默认的 ARC 被认为比在 Zig 代码中层层传递 allocator 更符合人体工学。
- 反方观点:在 Zig 中也可以使用全局 allocator,或将 allocator 附着到对象上,以避免过多样板代码。
- Odin、带有良好 API 和 arena 的 C,以及其他语言都被拿来说明:API 设计比语言本身更重要。
标准库规模 vs 生态碎片化
- 一派更偏好紧凑的 stdlib,以避免 C++ 式膨胀、促进更快演进,并减少过时 API 的长期维护成本。
- 另一派认为,小 stdlib 会迫使人们依赖大量第三方包,导致碎片化、多个相互竞争的库,以及依赖地狱。
- 示例:
- Python:通常认为大 stdlib 是优点,尽管部分模块已经过时;但流行的第三方库(例如 HTTP 客户端)在易用性上仍胜出。
- Go:stdlib 受到称赞,但具体部分(日志、flags、更早期的
net.IP)受到批评;较新的抽象如netip改善了这些问题。 - Rust:stdlib 中没有压缩功能被辩护为把不断演进的领域留给 crates;但也有人指出其他生态的 stdlib 里有很好的压缩器,作为反证。
- 建议的折中方案:小而核心的 stdlib + 官方维护的“认可”包,或者在后期采纳事实标准。
包管理器与依赖问题
- Zig 的包生态:
- 通过
build.zig.zon和 git 引用提供包管理;预计 0.12 的改进会带来提升。 - 按设计没有中央包仓库(类似 Go);有人认为这更安全,也有人认为这是个糟糕的决定。
- 通过
- 一般性担忧:
- 多个竞争性的 C/C++ 包管理器(vcpkg、conan 等)彼此不兼容。
- Rust 的
cargo:- 避免了按项目重复存放源码;死代码消除缓解了二进制膨胀。
- 被提及的问题:缓存很大、增量构建的旧产物陈旧;夜间版上即将加入的自动缓存 GC 受到欢迎。
- 有人建议使用 Nix 代替语言专属的包管理器。
字符串、Unicode 与类型系统保证
- Zig:
- 使用
[]const u8表示字符串;按惯例这些是 UTF-8。 - 函数通常会假定有效 UTF-8,而不会在运行时检查;责任由生产者承担。
- 批评者把这比作“只要别写 bug 就行”,并主张把编码不变量编码进类型里。
- 使用
- Rust:
- 区分
String(拥有的 UTF-8)和&str(借用的 UTF-8),在安全代码中会进行运行时 UTF-8 验证。 - 不安全构造函数允许跳过检查,但前提是程序员承诺其有效性。
- 像
OsString这样的独立类型避免了在不能保证 UTF-8 的地方强行要求 UTF-8。 - 这种模型因安全性和灵活性受到赞扬,但也因复杂性和学习成本受到批评。
- 区分
- Go 和其他语言:
- Go 的
string“只是字节”;可以包含任意数据,不保证 UTF-8。
- Go 的
- Unicode 支持:
- Zig 的
std.unicode覆盖了验证和码点迭代;更高级的支持(grapheme、分类)在外部库中。 - Rust 和 Go 中也存在类似缺口;完整的 Unicode 处理(例如 grapheme 迭代、基于语言环境的排序)很复杂,通常交给库来完成。
- Zig 的
- 有人认为,由于“字符串”的需求差异极大(可变性、切片、Unicode 语义、性能),因此生态中出现多种字符串抽象是自然的,即使存在内建类型。