首先,Rust 没有正式的语言规范。我的意思是,尽管对语法和对象等方面进行了解释,但没有正式的规则来描述语言特性可以是什么或不可以是什么。在 ISO C 语言标准里,几乎每一项都有三到四个描述片段:正式的语法约束 (即哪些东西是不被允许的或者不能用它完成哪些事情)、语义 (即它可以做什么、它是如何影响程序的、有哪些需要注意的地方),而且可能还会列出一些例子。中是这样描述结构体的:语法 (没有异议)、类似“结构体是用关键字 struct 定义的名义结构体类型”这样的定义、示例、在示例中间简短地提到空结构体,最后以“结构体没有指定精确的内存占用”结尾。我知道添加新特性比写文档更重要,但这样做确实很蹩脚。
一门成熟的编程语言 (版本到了 1.0) 应该有正式的规范,对于开发编译器的人和使用这门语言的程序员来说都应该有用。例如,对于结构体的定义,我发现至少缺少了这些东西:提到你可以 impl(实现)、将元组拆分成独立的项、说明为什么有匿名的元组而不是匿名的结构体,当然,还要使用适当的布局,让示例中重要的信息 (例如关于内存占用) 不至于丢失。
现在说说我经常遇到的问题,我不知道是我理解错了还是编译器理解错了。而且,由于没有正式的规范,我不知道是哪个出了问题 (即使更有可能是我理解错了)。
调用函数 / 方法。看看这个简单的示例:
struct Foo { a: i32 } impl Foo { fn bar(&mut self, val: i32) { self.a = val + 42; } } fn main() { let mut foo = Foo { a: 0 }; foo.bar(foo.a); }因为使用了借用,这个无法正常编译,但问题是编译器不是应该“聪明”地在调用之前创建一个 foo.a 的副本吗?我不确定,但 IIRC 的当前实现首先可变地借用对象,然后尝试借用参数。真的是这样吗?如果是,为什么是这样?有人告诉我,新版本编译器处理得很好,但问题仍然存在 (这是编译器的问题还是调用定义发生变化了?)
另一个是关于函数参数求值的警告。这里有一个简单的例子:
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,因为没有正式的规范告诉你它应该是怎样的。如果你要通过索引访问调用者对象,比如 handler[iter.next().unwrap() as usize].process(iter.next().unwrap()),情况会更糟糕。
另一个让我抓狂的是 trait。对于我来说,理解所有权、生命周期、借用这些概念都没什么问题,但 trait 几乎每次都会让我抓狂。我隐隐约约地知道这是“因为 trait 被实现成调用表”,但问题是它们应该被这样实现吗?它们的约束是什么?当你有一个超级 trait(例如,trait Foo: Bar),你就不可能在不编写大量样板代码的情况下轻易地将它转换成子 trait(例如 &Foo -> &Bar)。更糟糕的是,如果你将一个对象转成 Box,就没有办法找回原来的对象。重申一下:问题不在于我笨,而在于 Rust 缺乏描述,比如如何实现才是对的、为什么我想要的东西会如此之难。然后,我意识到我需要修改自己的代码来绕过这些限制。