十年 Rust 老将,痛心揭示其“三宗罪”!

尽管 Rust 以其强大的内存安全性、零成本抽象以及出色的并发支持赢得了广泛赞誉,但在长期的使用过程中,一位在 Rust 世界深耕了十年之久的资深开发者表示,他遇到了一些让他感到失望的地方。

原文链接:https://www.reddit.com/r/rust/comments/1e978l7/ive_used_and_loved_rust_for_10_years_here_are_the/

作者 | Lord_Zane       翻译 | 郑丽媛

出品 | CSDN(ID:CSDNnews)

到目前为止,我已经使用 Rust 大约 10 年了,从 2015 年 5 月 Rust 1.0 发布前不久就开始了。这些年来,我用 Rust 开发过各种项目,包括桌面 GUI 应用、服务器后端、命令行程序、通过 WebAssembly 实现沙盒脚本接口,以及多个与游戏相关的项目。就在最近,我还对 Bevy 游戏引擎做出了大量贡献。

除了 Rust,我还掌握了其他几种编程语言:Java、Python、TypeScript、Elixir、C 语言等,还有一些较为小众的语言,但相关的开发经验也相对较少。虽然不能说我在这些语言上是专家,但大体上我也很熟悉它们并了解其主要特点。在本文中,我主要会将 Rust 与 Java 进行比较,因为这是我除了 Rust 之外最常用的语言。

提前声明一下:在所有这些语言中,我最喜欢 Rust,之后也并不打算改变这个想法。我每天都在用它编程,大部分时间里都感到非常愉快。但就像其他语言一样,Rust 也有一些问题,而我想讨论的是那些反复出现的重大痛点。这些问题显著影响了我的工作效率,除非进行根本性改变,否则无法解决。

3c8609632812ae8a9d6e807aba10faff.jpeg

以下是一些我不会涉及的问题列表:

  • 异步/等待(Async/Await):在我看来,Rust 中的 Async/await 其实相当不错。在没有额外开销或内置运行时、取消操作等限制条件下,它相当可靠。我记得在 Rust 2018 版本发布时,为了引入这一特性遇到了很大压力,但我认为最终结果还是很不错的。主要问题在于同步代码和异步代码的混合使用、Pin 特性、生态系统中的多种执行器,以及零成本是否真的值得权衡。这个问题已经被讨论了很多次,我没什么可补充的。也许虚拟线程会更好一些,即直接承担运行时的成本,但我也不确定。我觉得,现在我们在 Web 服务器等场景下单独使用异步特性已经相当稳固了。

  • 库生态系统:我希望它能更稳定、更少 bug(例如,将 winit 与 sdl 进行对比),但这并不是语言本身的问题。关于这一点,我没有太多可以讨论的内容。

现在,来谈谈我对 Rust 的一些意见吧。

e9c37c6c75e440623f1ab18d70775d3d.png

Result<T, E>

当我刚开始接触 Rust 时,我很喜欢错误处理只是另一种类型的设计理念:隐式错误非常讨厌;强制用户意识到一个函数可能会出错,并处理这个错误是一个很棒的设计!

然而,随着我多年来在库和应用代码中都使用了 Rust,我对这种设计理念越来越失望。

作为一个库的作者,必须为每一个可能出现的问题创建新的错误类型、并在它们之间进行转换,真是让人头疼。最糟糕的情况莫过于你添加一个依赖项、调用其中的一个函数,然后还得想办法把它的错误类型添加到你的包装错误类型中。像 thiserror 这样的库可能稍微好点,但仍然不是很好的体验。这还只是调用一个函数的情况——如果你写了第二个不同功能的函数,可能就又需要一个全新的错误类型了。

然后是应用代码。通常情况下,我们不会关心一个函数失败的具体原因或方式,只想把错误上传并向用户显示最终结果。当然,有 anyhow 这样的库可以部分解决这个问题,但根据我的经验,Java 在这方面处理得更好。除了明显想要有一个动态分派类型的问题外,对我来说真正的问题是回溯信息。

在 Java 中,我可以看到一个完美的日志记录,确切地知道哪个函数首先抛出了错误,以及这个错误是如何传播到程序的日志记录或显示机制中的。而在 Rust 中,每当你使用 ? 运算符传播错误时,是没有回溯信息的。当然,回溯信息会有性能开销,这也就是为何它不是内置功能的原因。

库也会遇到这个问题——当用户报告一个 bug 时,很难搞清楚具体的问题所在,因为你所得到的只是“顶层函数失败”的信息,没有回溯信息,除非它是 panic。同样,追踪依赖项自身为什么会抛出错误也很困难。

Rust 在“迫使开发者考虑错误”这一点上做得很好。与 Java 不同,一个函数可能会失败这一点在 Rust 中非常明确,你不可能不小心忽略这个问题。我在其他语言中见过很多 bug,例如某个函数抛出了错误导致整个程序崩溃,而它本应该在更低层级通过重试等方式来处理。

然而,尽管它是零成本且非常明确的,但我认为 Rust 在认为人们会关心一个函数失败的具体原因方面犯了一个错误。我真的认为 Rust 该标准化一个单一的类型,类似于 Box<dyn Error>(包括支持字符串错误),并当错误在函数之间传播时自动附加上下文。这不适用于所有情况,因为它不是零成本的,也不是那么明确,但对于很多情况来说是有意义的。

顺便提一下,还有错误信息的问题。错误信息应该格式化为“Error: Failed to do x.”还是“Failed to do x”?是否需要句号结尾?首字母大小写?这不完全是语言本身的问题,但我希望有一个全生态系统的标准来格式化错误信息。

0b73b936fd0a67301aa0e452d83f08db.png

模块系统

有时孤儿规则确实令人头疼,而模块系统可能过于灵活了。

(注:孤儿规则是 Rust 中的一个规定,简称 OR。当为某类型实现某 trait 时,要求类型或 trait 至少有一个是在当前crate中定义的,不能为第三方的类型实现第三方的 trait。以此确保他人编写的代码不会破坏你的代码,也能避免你不小心破坏了其他不相关代码的情况。)

在维护 Bevy 项目时,我对此深有感触。Bevy 是一个单仓库项目,包含 bevy_render、bevy_pbr、bevy_time、bevy_gizmos、bevy_ui 等多个模块,还有一个顶层的 bevy 包用来重新导出所有内容。

在 crate 之间组织代码相当困难。你可以随意在 crate 之间重新导出类型,将某些部分设为 pub(crate)、pub(super) 或 pub(crate::random::path)。对于导入,同样的问题也适用,你可以选择重新导出特定的模块或类型。这样很容易无意中暴露你不打算公开的类型,或者重新导出一个模块时丢失了为其编写的模块文档。

这不仅仅是一个实际问题,而是因为模块系统赋予了过多的灵活性。这很奇怪,因为 Rust 喜欢显式,但在如何安排类型方面却给了你很大的自由度。无论你对 Java 的“一个文件就是一个类;模块路径遵循文件系统文件夹”的做法有何看法,至少它是明确的。在 Java 中进入一个大型项目并准确知道某个类型的位置要比在 Rust 中容易得多。

孤儿规则也是一个问题,但这个问题我没有太多要说的。即使对于库开发者来说,由于一个项目需要跨 crate 拆分(而 Rust 鼓励你将东西拆分成多个 crate)也会遇到不少麻烦。

2cedcd203bc7a2a5cb97aa366d7dd459.png

编译时间和 IDE 工具

我觉得,编译时间和 IDE 中的错误检查太慢了。尽管许多人在加快 rustc 和 rust-analyzer 方面做了很多出色的工作,我也无意贬低他们的努力,但从根本上说,Rust 将 1 个 crate 视为 1 个编译单元,这确实损害了最终用户的体验。在 Bevy 的 monorepo 中触碰一个函数意味着整个 crate 都会被重新编译,以及所有依赖它的其他 crate。我真的希望修改一个函数实现/文件,就像重新编译那个函数/文件并修补二进制文件一样简单。

Rust analyzer 也有同样的问题。IntelliJ 在启动时会对我的项目进行一次索引,并在剩余的开发时间里立即显示错误。而 Rust analyzer 则感觉每次你输入时都在重新索引整个项目。对于小项目来说还好,但在 Bevy 这样的规模上几乎无法使用。

我不是一个编译器开发者——也许这些都是无法解决的基本问题,尤其是考虑到宏、编译脚本、cargo 特性和其他问题。但我真的希望编译器能够维护一个项目结构图,并检测到我只修改了这一部分。既然用 VDOM 进行 UI 开发时能做到这一点,那么在 cargo/rustc 中怎么就不能实现呢?

这篇文章就到这里了。以上我所说的一切可能都只基于表面,没有深入研究过这些问题的现有讨论。尽管如此,这些是过去几年困扰我的主要问题,我很想听听其他人是否也面临同样的问题。

推荐阅读:

▶太贵了!Oracle让Java SE按“人头收费”的550天后,最新报告:86%的人想弃用

▶QQ 客户端性能稳定性防劣化系统 Hodor 技术方案

▶年薪平均减少1万美元,76%的人在用AI开发!调查了6.5万名开发者,Stack Overflow年度报告出炉!

e8deb652be922a83a2796abba6b40130.gif

能学习到新知识、产生共鸣,解答久困于心的困惑,这是《新程序员》的核心价值。欢迎扫描下方二维码订阅纸书和电子书。

42d447e54dae8b77a1dc9cbad31cbd5f.png

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CSDN资讯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值