为什么Rust不是一种成熟的编程语言

2020-09-19 23:21:30

虽然我对Rust本身没有什么反对意见,并且一直在用Rust编写我喜欢的项目,但是我发现仍然有一些不足之处阻碍了Rust成为一种合适的编程语言。在这里,我想介绍它们,并解释为什么我认为它们是这样的,即使不是所有的它们对我都有影响。

首先,也是最重要的是,Rust没有正式的语言规范,我的意思是,虽然解释了语法和对象等一些内容,但是没有正式的规则来描述哪些语言功能可以,哪些不可以。如果您曾经看过ISO C标准,您会看到几乎任何实体的描述都有三到四个部分:形式语法、约束(即什么是不允许的或不能做的)、语义(即它做什么、它如何影响程序、有什么实现注意事项),可能会有一些例子来说明这些观点。Rust中最接近的等价物是Rust引用,例如,那里的结构按以下方式描述:语法(那里没有异议),以“结构是用关键字struct.定义的名义结构类型”的形式定义,示例,在示例中间简要提到空(或类似单元的)结构,以及“没有指定结构的精确内存布局”。在最后。我知道添加新特性比记录它们更重要,但这太差劲了。

一种真正成熟的语言(其版本为1.0)应该有一个正式的规范,该规范对于开发编译器的人员和试图理解该语言的某些复杂之处以及为什么它不能按预期工作的程序员都是有用的(稍后将详细介绍)。例如,对于那个结构定义,我发现至少缺少这些:提到您可以对它执行Iml(即使引用也可以-即使您必须为每种类型重复它),将元组拆分到单独的条目中,因为它在语法上非常不同,并引发了一个问题,为什么您使用匿名元组而不是匿名结构(您在文档中也找不到),当然,还要创建适当的布局,以便相当重要的信息(例如,关于内存布局)不会在示例中丢失。

现在来谈谈我经常遇到的具体问题,我不知道是我理解错了,还是编译器理解错了。由于没有正式的规范,我不知道是哪一个(即使前者是最有可能的)。

Struct foo{a:i32}impll foo{fn bar(&;mut self,val:I32){self.a=val+42;}}fn main(){clet mut foo=foo{a:0};*foo.bar(foo.a);}。

现在,由于借用,这还不能编译,但是编译器不应该足够聪明,在调用之前创建foo.a的副本吗?我不确定,但IIRC当前实现首先可变地借用对象进行调用,然后才尝试借用参数。真的是这样吗?如果是,原因何在?更新:我听说较新版本的编译器可以很好地处理它,但问题仍然存在(只是编译器问题还是调用定义已更改?)。

另一件事是对函数参数求值的古老的C警告。下面是一个简单的示例:

那么是foo(';a';,';b';,';c';)还是foo(';c#39;,';b';,';b';)调用呢?在C语言中,它是未定义的,因为它取决于参数在当前平台上是如何传递的(如果您不记得__Pascal或__stdcall,就认为自己很幸运)。在Rust中,它是未定义的,因为没有格式规范可以告诉您这么多。如果您考虑到您可能在某些理论字节码处理程序中使用相同的源来索引调用者对象,比如handler[iter.next().unwork()as usize].process(iter.next().unrapp());(当然,这是一种可怕的代码编写方式,您应该使用命名的临时变量,但它应该说明问题),情况会更糟。

另一个让我烦恼的原因是性格。我在拥有/终生/借用概念上几乎没有问题,但特质几乎每次都会让我着迷。我隐约知道为什么存在以下问题的答案是“因为特征是作为调用表实现的”,但是同样,它们是否应该像这样实现,以及对它们的约束应该是什么(毕竟,原始对象应该以某种方式链接到特征指针)。因此,当您有一个上级特征(即特征foo:bar)时,如果不编写大量的样板代码,就不能很容易地将其转换为子特征(例如&;foo->;&;Bar)。更糟糕的是,如果将对象转换为Box<;Characteristic>;,就无法取回原始对象(当然仍然是盒装形式;我记得看到过一个特殊的箱子,它实现了许多样板代码,以便获得可变的引用)。重申一下:问题不是我太笨,而是缺乏正式的描述,说明我是如何做到的,以及为什么我想要的东西这么难。然后,我可能至少能够意识到我应该如何更改代码以绕过这些限制。

不,我不打算谈论编译速度。这当然是件讨厌的事,但本身不是问题。在这里,我想指出一种成熟的语言不应该存在的相当多的理论问题。而只有一个编译器就是这些问题之一(称之为问题零)。

首先,自举过程糟糕得可笑。我知道这从来都不是太容易,但是如果你称自己是一门系统编程语言,你应该能够在正常的步骤中引导编译器。例如,IIRC Guix对C编译器有以下引导过程:Scheme中的Simple C编译器(您通常可以手动编写汇编实现)编译tcc,tcc编译GCC 2.95,GCC 2.95编译GCC 3.7,GCC 3.7编译GCC 4.9。对于rustc,你要么从用OCaml编写的原始编译器开始,然后用前一个版本(即1.17和1.16)编译每个后续版本,要么欺骗使用用C++编写的mrustc,mrustc可以编译Rust 1.19或1.29(没有借用检查),然后用1.29编译1.30,用1.30编译1.31等等。这里的问题是你不能跳过版本,例如用rustc 1.36编译rustc 1.46(我很高兴知道我错了)。我想你应该有一个低效的编译器,但是用一种更老的编译器应该能理解的方言编写,也就是说,rustc 1.0应该能够编译1.10的编译器,它可以用来编译1.20,以此类推。当然,对于比较理论性的问题来说,这是对资源的巨大浪费,但事实证明,这对编译器设计本身是有益的。

然后是LLVM依赖。我知道LLVM有很多优点(比如不需要担心许多平台的代码生成和优化),但它也有一些缺点。首先,您没有一个真正的自托管编译器(这是一个理论问题,但仍然是一个值得考虑的问题;还要考虑到,您必须依赖一个主要由大公司开发的框架,该框架主要由大公司开发,主要是为了他们自己的利益)。其次,你受到它功能的限制,例如,我读到关于调试构建太慢的抱怨,这主要是因为LLVM后端。我怀疑它仍然不能做某些与内存相关的优化,因为它的设计考虑到了C++编译器,而C++编译器对于多个内存访问仍然有某些怪癖(另外,IIRC在Rust代码中有一个由无限循环触发的LLVM错误,在那里是完全有效的,但不符合C++规则)。我知道起重机的存在(GCC的起重装置是铁锈的前端),所以希望它能得到改善。

最后,还有一个与前面的问题有关的问题。生锈对装配的支撑性很差。当然,需要独立汇编和内联汇编的人并不多(除了ASM之外,这仍然是缺乏的!)。几乎已经存在),但是面向系统编程的语言除了支持高级语言代码外,还支持编译汇编语言,因此即使预处理器语法不像GAS那样丰富,支持汇编语言文件也是合适的。摆弄build.rs来调用外部汇编程序是可能的,但一点也不好。

还有一个我应该提一下的关于锈病病库的问题。它对操作系统的接口毫无用处。现在,如果我想做一些对任何UNIX系统都很自然的事情,我至少需要导入libc机箱并链接到外部libc(无论如何它是运行时的一部分)。一种解决方案是我听说的板条箱想要将MUSL转换为铁锈,这样您至少可以省去链接步骤。但是正确的解决方案应该是在STD crate中至少支持特定于操作系统的syscall(),因为许多有趣的libc函数只是它的包装器(比如open()/write()/ioctl();Windows是不同的,所以我不介意它是否是std::OS::Unix::syscall,而不是更常见的东西)。

我不是一个铁锈语言架构师,我极不可能成为一个,但我