## 一、一个 Rust 用户的坦诚
先说清楚:我并不反对 Rust 本身。
事实上,我一直在用 Rust 开发自己的项目。我喜欢它的所有权系统,理解生命周期和借用检查,也享受它带来的内存安全保证。
但正是因为每天都在用,我才更清楚地看到它的不足——那些让它无法成为一门**真正成熟**的编程语言的问题。
这些问题对我个人开发没什么影响,但从一门语言的长远发展来看,它们很重要。
## 二、没有正式的语言规范
这是最基础,也是最致命的问题。
Rust 已经到了 1.0+ 版本,却没有正式的语言规范。
对比一下 ISO C 标准:每一项特性都有三到四个描述片段——正式的语法约束、语义定义、注意事项、示例代码。清晰、完整、可验证。
再看看 Rust 参考文档对"结构体"的描述:
- 语法(没问题)
- "结构体是用关键字 struct 定义的名义结构体类型"
- 示例
- 简短提到空结构体
- "结构体没有指定精确的内存占用"
**结束。**
这就完了?
至少缺少这些内容:
- 你可以对结构体实现 trait(impl)
- 元组如何拆分成独立项
- 为什么有匿名元组却没有匿名结构体
- 内存布局的详细说明
我知道添加新特性比写文档更重要,但这样做确实很蹩脚。
一门成熟的编程语言(版本到了 1.0)应该有正式的规范,对编译器开发者和语言使用者都有用。
## 三、歧义与未定义行为
没有正式规范的直接后果:我经常遇到不确定的行为,不知道是我理解错了,还是编译器理解错了。
### 示例 1:函数调用顺序
```rust
let mut iter = "abc".chars();
foo(iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap());
```
问题来了:是调用 `foo('a','b','c')` 还是 `foo('c','b','a')`?
在 C 语言中,这是 undefined,因为取决于参数传递方式。
**在 Rust 中,这也是 undefined——因为没有正式规范告诉你它应该是怎样的。**
### 示例 2:借用检查的不确定性
```rust
struct Foo { a: i32 }
fn bar(foo: &mut Foo, x: i32) { foo.a = x; }
let mut foo = Foo { a: 0 };
bar(&mut foo, foo.a);
```
这段代码因为借用问题无法编译。但问题是:编译器不是应该"聪明地"在调用前创建 `foo.a` 的副本吗?
有人告诉我新版本编译器处理得很好,但问题仍然存在——这是编译器的问题,还是调用定义发生了变化?
**没有规范,我不知道。**
## 四、Trait 系统的复杂性
理解所有权、生命周期、借用,这些都没问题。
**但 trait 几乎每次都会让我抓狂。**
我知道 trait 被实现成"调用表"(vtable),但问题是:
- 它们应该被这样实现吗?
- 约束是什么?
- 为什么超级 trait(`trait Foo: Bar`)转换成子 trait(`&Foo -> &Bar`)需要大量样板代码?
- 为什么 `Box` 转换后无法找回原对象?
问题不在于我笨,而在于 Rust 缺乏描述:
- 如何实现才是对的
- 为什么我想要的东西会如此之难
最后,我只能修改自己的代码来绕过这些限制。
## 五、编译器的问题
### 单一编译器的风险
一门成熟的编程语言不应该只有一个编译器。这是 Rust 的另一个问题。
**自举过程非常糟糕。**
看看 Guix 对 C 编译器的自举:
1. 手动编写汇编实现简单 C 编译器
2. 用它编译 TCC
3. TCC 编译 GCC 2.95
4. GCC 2.95 编译 GCC 3.7
5. GCC 3.7 编译 GCC 4.9
清晰、可控、可验证。
Rust 呢?
- 要么用原始 OCaml 编译器,然后版本链式编译(1.16 → 1.17 → 1.18...)
- 要么用 mrustc(C++ 编写)编译 1.19/1.29,再逐步升级
**关键问题:你不能跳过版本。**
不能用 rustc 1.36 直接编译 1.46。这意味着什么?如果中间某个版本有问题,整个链条就断了。
理论上,应该有一种"方言"编译器,用老版本能理解的方式开发新版本,实现更优雅的升级路径。
### LLVM 依赖的束缚
LLVM 确实有很多优点:跨平台代码生成、优化、不用自己实现后端。
但它也有代价:
1. **没有真正的自托管编译器** —— 理论上 Rust 编译器应该能用 Rust 完全编写
2. **调试构建速度慢** —— 很多人抱怨,主要是因为 LLVM 后端
3. **内存优化受限** —— LLVM 设计参考了 C++ 编译器,仍有奇怪的多内存访问问题
好消息是现在有 Cranelift,希望这个问题能改善。
### 汇编支持薄弱
面向系统编程的语言,除了高级代码外还应该支持汇编。
Rust 对汇编的支持很差。虽然可以用 `build.rs` 调用外部汇编器,但"一点也不好"。
一门真正的系统语言,应该原生支持汇编文件,即使不像 GAS 那样提供丰富的预处理器语法。
## 六、标准库的局限
Rust 标准库对操作系统交互的支持很有限。
如果我想对任意 UNIX 系统做一些事情,至少需要:
- 导入 `libc` crate
- 链接到外部 libc(运行时的一部分)
**解决方案?**
一种是把 musl 翻译成 Rust,省掉链接步骤。
但更好的方案是:标准库直接支持 `syscall()`。
因为很多有趣的 libc 函数只是对 `syscall()` 的包装(`open()`/`write()`/`ioctl()` 等)。如果标准库能直接调用 syscall,就能摆脱对 C 库的依赖。
## 七、总结:成熟之路还有多远?
我不是 Rust 的架构师,也不可能成为。
但我知道,Rust 要成为一门**真正成熟的、适合系统开发的编程语言**,还缺少一些东西:
| 缺失要素 | 现状 | 理想状态 |
|---------|------|---------|
| **正式规范** | 参考文档不完整 | ISO C 级别的标准文档 |
| **完全自托管** | 依赖 LLVM,自举链条脆弱 | 真正的自托管编译器 |
| **底层交互** | 依赖 C 库和外部汇编器 | 原生支持 syscall 和汇编 |
这些问题不影响我用 Rust 写项目,但它们决定了 Rust 能走多远。
**希望这些问题能够得到解决。**
---
## 写在最后
写这些不是为了黑 Rust。
恰恰相反,正是因为我希望 Rust 更好,才愿意花时间指出这些问题。
一门语言成熟的过程,就是不断面对问题、解决问题的过程。
C 语言用了几十年才标准化,C++ 至今还在演进。Rust 还很年轻,有时间,也有机会。
但前提是:**社区和团队愿意承认这些问题,并着手解决。**
---
*你怎么看 Rust 的这些不足?是"吹毛求疵"还是"切中要害"?欢迎在评论区分享你的观点。*
登录后可参与表态
讨论回复
0 条回复还没有人回复,快来发表你的看法吧!