我眼中的“Rust 之魂”
原文 | 日期:2022-09-19 10:15 -0400
重读我之前这篇文章,我觉得我应该澄清一下为什么我称它为“Rust 之魂”。在我看来,Rust 之魂绝对不是明确的分配。
相反,这是几个关键价值观之间的斗争,尤其是在与透明度的对立的生产力和通用性1方面。
Rust 的目标一直是高级别,但拥有低级别的表现力和控制力。通常情况下,我们能够找到一种“第三条路”来消除权衡,来很好地达成这两个目标。
但找到那些“第三条路”需要时间,有时我们只能暂时接受对某个价值观的某种削弱,就能取得进展。
正是在这些时候,当我们不得不做出艰难的决定时,关于“Rust 之灵魂”的问题开始发挥作用。我一直在思考这个问题,所以我想我应该写一篇文章,扩大透明度在 Rust 中的作用,以及围绕它产生的一些矛盾之处。
我在最初的帖子中没有写到通用性 (versatility):相反,我当时主要考虑对生产力的削弱。但正如我现在所想的,通用性才是真正发挥作用的地方。 通用性实际上意味着 Rust 对高级别和低级别的东西都很有用,我认为要求显式的 dyn 转换器无疑是对高级别的削弱。有趣的是,我在列表中把通用性放在了透明度之后,这意味着它的优先级较低,这似乎支持了拥有某种明确的转换器的决定。
为什么我们看重透明度?
根据 Rustacean 的原则草案:
透明的:你可以预测和控制低级别的细节。
众所周知,C 语言与机器通常的操作方式非常接近。它们如此接近,以至于人们有时将 C 称为“便携式汇编” (portable assembly)2。
C++ 和 Rust 都试图继承这一传统,但增加了更高级别的抽象。这不可避免地会导致对立局势。例如,运算符重载会使弄清楚 a + b
更加困难3。
在这一点上,一些人指出实际上隐藏在 C 代码中的无数细微之处和细节。嘘,别说了。
我记得一位同事在过去的工作中发现,有人重载了代码库中的 ->
操作符。他们发出了一封愤怒的电子邮件:“什么时候停止重载运算符?我必须检查代码中的每一处和乱七八糟的东西吗?(注:Rust 支持重载 deref 操作符。)
透明度给你控制权
透明度不会自动带来高性能,但它确实会带来控制权。这在构建系统时很有帮助,因为你可以将其设置为做你想做的事情,但在分析其性能或调试时也会有所帮助。
没有什么比开始编写代码几个小时后才意识到问题的根源并不在你可以看到的代码中的任何地方更令人沮丧的 —— 它存在于一些看不见的交互中,而不是显式的。
透明度可能降低性能
透明度的另一面是过度规范。程序越直接地映射到汇编,编译器和运行时必须做的聪明事情的空间就越小,这可能会导致性能降低。
在 Rust 中,我们总是在寻找不那么透明的地方,以获得性能 —— 当然只在一定程度上。
结构布局就是一个例子:Rust 编译器保留了对结构中的字段重新排序的自由,使我们能够创建更紧凑的数据结构。
这没有 C 那么透明,但通常你不用关心这些。(当然,如果你想指定字段的顺序,Rust 提供了 #[repr]
属性)
透明度降低通用性和生产力
然而,透明度的更大代价是通用性。它迫使每个人关心低级别的细节,这些细节实际上可能对手头的问题并不重要。4
大多数 Rust 异步系统与 dyn async trait 相关,例如执行或多或少的分配。
异步函数调用 Box::new
不太可能是性能问题。对于这些使用者来说,选择 Boxing 转换器增加了他们必须管理的总体复杂性,从而获得很少的收益。
如果你从事的是一个不需要顶级性能的项目,这将使 Rust 的吸引力低于其他语言。我不是说这不好,但这是事实。
换句话说,对一件事保持透明会让其他事情变得更加模糊(“只见树木不见森林”)。
一种零和情形...
在 async trait 当前的设计中,我们正在努力解决一个核心问题:“Rust 有多通用”。
眼下,感觉就像是“零和局面”。我们可以添加像 Boxing::new
这样的东西来保持透明度,但这会让我们失去一些通用性,希望不会太多。
...就目前而言?
不过,我确实想知道,是否有“第三条路”在某个地方等着你。我在上一篇文章中暗示了这一点。
目前,我不知道第三种方法是什么,我认为要求显式的转换器是最实际的方法。但在我看来,这还不完美,我希望我们能够将其归入更一般的东西。
一些可能导致“第三条路”的因素:
- with-子句:我对 with 子句 的概念和一般的作用域内的功能很感兴趣。也许我们能将“默认转换器”看作是通过 with-子句指定的?
- 常量求值:常量求值的一个更好的用途是用于定制 Rust 编译方式的“元编程”。例如,我们可能会让你编写一个为给定 trait 创建 vtable 数据结构的
const fn
- 配置文件和可移植性:我们能不能找到一种更好的方法来识别你想要的透明度,也许是通过某种“配置文件”?我觉得现在已经有了事实上的配置文件,但我们不认识他们。 no-std 是一个明显的例子,但另一个例子是你试图支持的一组操作系统或架构。了解不同的使用者有不同的需求,并给人们一种选择最适合他们的方法, 可能会让我们更好支持地所有使用者 —— 但话又说回来,这可能会让 Rust 变得“模式化”和更令人困惑。有什么评论吗?
评论
请在 此处帖子 中留下评论。谢谢!