低代码到底是不是行业毒瘤?一线大厂怎么做的?戳此了解>>> 了解详情
写点什么

Rust 的不足之处,让它无法成为一门成熟的编程语言

2020 年 10 月 16 日

Rust的不足之处,让它无法成为一门成熟的编程语言

虽然我并不反对 Rust 本身,并且一直在用 Rust 开发自己的项目,但我还是发现它有一些不足之处,让它无法成为一门成熟的编程语言。在这篇文章里,我想把这些问题列出来,并解释为什么我会这样认为,即使它们对我没有任何影响。


Rust 语言本身的问题

首先,Rust 没有正式的语言规范。我的意思是,尽管对语法和对象等方面进行了解释,但没有正式的规则来描述语言特性可以是什么或不可以是什么。在 ISO C 语言标准里,几乎每一项都有三到四个描述片段:正式的语法约束(即哪些东西是不被允许的或者不能用它完成哪些事情)、语义(即它可以做什么、它是如何影响程序的、有哪些需要注意的地方),而且可能还会列出一些例子。Rust 参考(https://doc.rust-lang.org/reference/)中是这样描述结构体的:语法(没有异议)、类似“结构体是用关键字 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 缺乏描述,比如如何实现才是对的、为什么我想要的东西会如此之难。然后,我意识到我需要修改自己的代码来绕过这些限制。


rustc 的问题

其实我不是想讨论编译速度问题,尽管有点扰人,但它本身并不是个问题。我想指出的是一些一门成熟的编程语言不应该有的问题,而一门语言只有一个编译器就是其中的一个问题。


首先,自举过程非常糟糕。我知道这并不容易,但如果 Rust 被认为是一门系统编程语言,那么就应该能够通过几个步骤来自举编译器。例如,IIRC Guix 对 C 编译器的自举过程:用简单 C 编译器(通常可以通过手动编写汇编代码实现)编译 TCC,用 TCC 编译 GCC 2.95,用 GCC 2.95 编译 GCC 3.7,用 GCC 3.7 编译 GCC 4.9。对于 rustc 来说,你要么使用原始的编译器(使用 OCaml 开发的),然后使用前一个版本编译后一个版本(即使用 1.16 编译 1.17),要么使用 mrustc(使用 C++开发的),它可以编译 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++编译器,而后者仍然存在奇怪的多内存访问问题。我知道现在有 cranelift,所以希望这个问题可以得到改善。


最后,还有一个与上一个问题相关的问题。Rust 对汇编的支撑很差。当然,并不是所有人都需要汇编支持,但面向系统编程的语言除了支持高级语言代码外还应该要支持编译汇编,所以也应该支持汇编文件,即使不像 GAS 那样提供了丰富的预处理器语法。我们可以用 build.rs 来调用外部汇编器,但这样一点也不好。


其他问题

Rust std 库有一个问题,它对于操作系统的交互来说并没有什么用。如果我想对任意一个 UNIX 系统做一些事情,至少需要导入 libc,并链接到外部 libc(它是运行时的一部分)。一种解决方案是把 musl 翻译成 Rust,这样至少可以省掉链接步骤。但更好的解决方案应该是支持使用 std 里的 syscall(),因为很多有趣的 libc 函数只是对 syscall()进行了包装(例如 open()/write()/ioctl())。


我不是 Rust 的架构师,也不可能成为 Rust 的架构师。但是我知道,Rust 要成为一门成熟的适合系统开发的编程语言,缺少了一些东西(本质上就是:完全自托管、规范和能够不借助 C 编译器和汇编实现与底层的交互)。希望这些问题能够得到解决。


原文链接:https://codecs.multimedia.cx/2020/09/why-rust-is-not-a-mature-programming-language/


2020 年 10 月 16 日 16:073807

评论 3 条评论

发布
用户头像
这译文读起来有够费劲的
2020 年 10 月 17 日 17:14
回复
文章最后有原文链接
2020 年 10 月 18 日 18:19
回复
infoq的又一篇hash翻译……
2020 年 10 月 26 日 18:32
回复
没有更多了
发现更多内容

你真的理解透彻高并发了吗?来看看架构师眼里的高并发

小谈

Java 面试 高并发 高并发系统设计

Android架构组件-App架构指南,你还不收藏嘛

小吴选手

架构 架构师 架构总结 架构要素 P7架构师

六月我在工作中蜕变,勤奋小人打架终于赢了

程序员小跃

效率工具 加班 沟通 复盘

架构师训练营 第 5 周作业

Lingjun

极客大学架构师训练营

今天来聊聊如何挑书

封不羁

读书 个人感想

锦囊篇|一文摸懂SharedPreferences和MMKV(二)

ClericYi

什么时候不要用微服务?以 Istio 为例

无予且行

Java 微服务 后端

饿了么4年,阿里2年:我的总结与思考

程序员生活志

工作经验

面试官:既然CPU有MESI,为什么 JMM 还需要volatile关键字?

犬来八荒

Java JVM 硬件 java面试

专科程序员与本科程序员之间有什么区别?薪资待遇又差多少?

码农月半

spring 程序员 程序员人生 Java 面试 程序员成长

spring 那点事儿——让你少走弯路

爱java爱自己

Spring Cloud Spring Boot

一个简单的技术选型心得

i风语

Java 架构

第四周

仪轩

SQLite你用对了吗

这小胖猫

sqlite 数据库 选型

如何搭建一个Zookeeper集群

Rayjun

大数据 zookeeper 分布式

简直了!顶级架构师分享心得,如何在项目中兼容多种数据库

犬来八荒

Java MySQL 数据库 面试

为什么建议项目中统一线程池类?

张挺

cms项目系列(一)——SSM框架搭建

程序员的时光

spring

1.2w字 | 初中级前端 JavaScript 自测清单 - 1

pingan8787

Java 前端 Web

【思考】互联网厂商争夺企业市场

superman

企业中台 互联网

Redis系列(五):你要的Redis集群搭建来了,实践与否你自己选!

z小赵

Java redis 分布式 高并发

面试细节: i = i++和 i = ++i

Java小咖秀

JVM Java 面试 经验分享

计算机操作系统基础(十一)---线程同步之互斥量

书旅

php laravel 线程 操作系统 进程

深入理解编译优化之循环展开和粗化锁

程序那些事

JIT 编译优化 循环展开 粗化锁

编程核心能力之组合

顿晓

Java 学习 pipe

源码分析 | 数据异构Canal 初探

小新

程序员阿里、京东、美团面试整理的面试题,测试一下你都会了吗?

小谈

Java 阿里巴巴 面试

解读 java 并发队列 BlockingQueue

猿灯塔

Java

我是如何解决邮件焦虑的

vinkyqy

效率 职场 邮件

18个Java8日期处理的实践,太有用了建议收藏

码哥小胖

MySQL SQL语法 sql查询

农产品电商平台的S曲线分析

石云升

增长 S型曲线 破局点

2021 ThoughtWorks 技术雷达峰会

2021 ThoughtWorks 技术雷达峰会

Rust的不足之处,让它无法成为一门成熟的编程语言-InfoQ