让我们尝试提出反对Rust的论点

我最近读了一篇批评Rust的文章。尽管有很多正确的方法,但我不喜欢它-太多了有争议。总的来说,我完全不建议阅读任何批评Rust的文章。这不好,因为讨论缺点很重要,而对低质量和无能的批评的诽谤却使真正好的论点被忽略。



因此,我将尝试反对Rust。



并非所有编程都是系统的



Rust是一种系统编程语言。它在运行时提供对数据组成和代码执行行为的精确控制,以实现最佳性能和灵活性。与其他系统编程语言不同,它还提供了内存安全性-有问题的程序以定义良好的方式终止,从而防止了(潜在的危险)不确定的行为。



但是,在大多数情况下,不需要绝对性能或对硬件资源的控制。对于这些情况,Kotlin或Go等现代托管语言通过使用动态内存托管垃圾收集器提供了不错的速度,令人羡慕的性能和内存安全性。



复杂



程序员的时间是昂贵的,而对于Rust,则必须花费大量时间来学习语言本身。社区一直在努力创建高质量的教材,但是语言非常庞大。即使使用Rust重写项目对您有利可图,但学习语言本身也可能太昂贵。



改进控制的代价是选择的诅咒:



struct Foo     { bar: Bar         }
struct Foo<'a> { bar: &'a Bar     }
struct Foo<'a> { bar: &'a mut Bar }
struct Foo     { bar: Box<Bar>    }
struct Foo     { bar: Rc<Bar>     }
struct Foo     { bar: Arc<Bar>    }


在Kotlin中,您编写了一个类Foo(val bar: Bar)并开始解决问题。在Rust中,您必须使用特殊的语法进行选择,有时甚至是重要的选择。



所有这些复杂性是有原因的-我们不知道如何创建一种更简单,更内存安全的底层语言。但是,并非每个任务都需要低级语言。



另请参见演示文稿,当Vaza下沉时,为什么C ++会继续浮动



编译时间



编译时间是一个普遍因素。如果某种语言的程序运行缓慢,但是该语言允许快速编译,则程序员将有更多时间进行优化以加快程序启动!



泛型困境中, Rust特意选择了慢速编译器。这是有道理的(运行时确实在加快),但是您必须为大型项目的合理构建时间而奋斗。



rustc实现了生产编译器中最先进的增量编译算法,但是有点像与语言的内置编译模型作斗争。



与C ++不同,Rust程序集不会并行化到极限,并行进程的数量受依赖图中关键路径的长度限制。如果要编译的内核超过40个,则差异会很明显。



在Rust中,也没有pimpl惯用语的类似物,因此更改板条箱要求重新编译(而不仅仅是链接)其所有逆相关项。



到期



五年绝对是短时间,所以Rust是一门年轻的语言。尽管前途一片光明,但十年后,我们很有可能将使用C而不是Rust进行编程(请参阅Lindy效果)。如果您已经从事软件开发工作数十年,则应该认真考虑选择新技术的风险(尽管回顾90年代,选择Java而非Cobol来开发银行软件是正确的选择)。



Rust只有一个完整的实现,即rustc编译器。最先进的替代性mrustc实现故意跳过许多静态安全检查。目前,rustc仅支持一个生产就绪的后端LLVM。因此,这里对处理器体系结构的支持要比具有GCC实现的C窄,并且对许多专有的特定于供应商的编译器的支持也要窄。



最后,Rust没有官方规范。当前规范不完整,没有记录一些次要的实现细节。



备择方案



除了Rust之外,还有其他用于系统编程的语言,包括C,C ++和Ada。



现代C ++提供了提高安全性的工具指南。甚至还有一个Rust风格的对象生命周期安全建议!与Rust不同,使用这些工具并不能保证没有内存安全问题。但是,如果您已经支持大量的C ++代码,则进行检查很有意义,也许遵循建议并使用消毒剂将有助于解决安全性问题。这很困难,但显然比用另一种语言重写所有代码容易!



如果您使用的是C,则可以应用形式方法来证明没有不确定的行为,或者只是彻底地测试所有东西



除非使用动态内存(从不调用free),否则Ada是内存安全的



Rust是一种有趣的安全性成本语言,但远非唯一!



工具集



Rust的工具并不完美。基本工具箱,编译器和构建系统(cargo)通常被称为同类最佳。



但是,例如,一些与运行时相关的工具(主要是用于堆分析)只是缺失了-如果该工具不存在,就很难考虑运行时!此外,IDE支持也远远低于Java的可靠性水平。在Rust中,根本不可能对具有数百万行的程序进行自动化的复杂重构。



积分



无论Rust承诺什么,当今的系统编程世界都使用C和C ++。Rust并非故意尝试模仿这些语言-它不使用C ++或C ABI风格的类。



这意味着需要在世界之间建立桥梁。集成将不会是无缝的,不安全的,并不总是具有成本效益的,并且需要语言之间的同步。集成工作在适当的地方并且工具箱正在融合时,由于整体的复杂性,一路上有时会遇到障碍。



一个具体的问题是,Cargo过于自信的世界观(对于纯Rust项目而言非常出色)可能使它难以与大型构建系统集成。



性能



“使用LLVM”并不是解决所有性能问题的万能解决方案。虽然我不知道总体上将C ++和Rust的性能进行比较的基准,但不难想到Rust的性能不如C ++。



可能最大的问题是Rust的移动语义是基于值的(memcpy在机器代码级别)。另一方面,C ++语义使用特殊的引用来获取数据(机器代码级别的指针)。从理论上讲,编译器应查看副本链,实际上,通常并非如此:#57077。一个相关的问题是缺乏新数据的分配-Rust有时需要将字节复制到堆栈中/从堆栈复制字节,而C ++可以在适当的位置创建对象。



有趣的是,默认的Rust ABI(为了提高效率而牺牲了稳定性)有时会比C:#26494表现差



最后,尽管在理论上Rust代码应该由于别名信息更加丰富而更加高效,但是启用别名相关的优化会导致LLVM错误和错误的编译:#54878



但是,这些都是罕见的例子,有时比较是朝另一个方向进行的。例如,在BoxRust中,没有任何性能问题,而在中则没有std::unique_ptr



一个潜在的更大问题是,Rust及其泛型定义的表现力不及C ++。所以一些公式化的技巧 不能以良好的语法在Rust中表达用于高性能的C ++。



不安全值



也许这个想法unsafe对Rust来说比所有权和借贷更重要。通过将所有危险操作划分为块unsafe和功能,并坚持为其提供更高级别的安全接口,可以创建一个同时执行以下操作的系统:



  1. 可靠(未经检查的unsafe代码不会导致未定义的行为),

  2. 模块化(不同的不安全块可以单独测试)。


很明显是这种情况:模糊的Rust代码发现恐慌,但没有缓冲区溢出。



但是理论上的前景并不光明。



首先,没有Rust内存模型的定义,因此不可能正式检查给定的不安全块是否有效。对于“ rustc所做或可以依靠的事情”,有一个非正式的定义,正在运行时验证程序上进行工作,但实际模型尚不清楚。因此,某处可能存在一些不安全的代码,这些代码今天可以正常工作,但是明天将被声明为无效代码,并在一年后进行新的编译器优化。



其次,据信不安全的块实际上不是模块化的。实际上,足够强大的不安全块可以扩展语言。这两个扩展在彼此隔离时没有做任何错,但是在并发使用时会导致未定义的行为:请参阅可观察到的对等和不安全代码。



最后,在编译器中存在明显的错误



以下是我特意省略的一些主题:



  • 经济学(“很难找到Rust程序员”)-我认为“成熟度”部分反映了这个问题的实质,它不仅限于鸡肉和鸡蛋问题。

  • 依赖关系(“ stdlib太小/到处都有太多依赖关系”)-考虑到Cargo和语言的相关部分的质量,我个人认为这不是问题。

  • 动态链接(“ Rust应该具有稳定的ABI”)-我认为这不是一个强有力的论点。从根本上讲,同构化与动态链接不兼容,如果您确实需要,则可以使用C ABI。我确实认为这里可以改进,但是我们不太可能谈论Rust的特定更改


/ r / rust中的 讨论主题



All Articles