Rustacean原则概要

电子说

1.3w人已加入

描述

也许这篇文章可以消除你对 Rust 的诸多误解。

背景

Rustacean 是对Rust 编程语言[1]的用户的称呼。所以,「Rustacean 原则」可以看作是 Rust 官方团队站在 Rust 语言用户角度上创造 Rust 语言时所参考的原则。

Rustacean 是 Rust +‎ crustacean 的混合词。crustacean 是指甲壳纲动物,所以 Rust 社区将螃蟹作为 Rust 语言的吉祥物,称之为 Ferris,中文叫摩天蟹。值得明确的是,该吉祥物并非 Rust 官方指定的,最初由 Karen 在 rustacean.net[2] 网站公布了 Ferris 的概念艺术图。

Rustacean 原则是由 Rust 语言团队 Leader Niko 在 2021 年所发起的项目[3],这个项目试图列举支配 Rust 设计和社区运作方式的原则。Niko 说,这些原则并非代表 Rust 官方而发布,只是他自己的观点。建立原则的重点在于尝试更好地发展原则并且在团队中使用。

虽然这些是实验性原则,但该原则在 Rust 官方内部经过一致性的讨论和认可。从 Niko 今天发布的最新博客Rust 异步trait Part8[4] 和 谈 “Rust 之魂”[5] 中也看得出来,该原则应该已经在使用了。

去年的时候,因为 Niko 这篇 Rust 原则的文章还引发了社区的一些争议。(前)Rust 核心团队成员 Steve Klabnik 认为 Niko 这篇原则是以亚马逊原则为蓝本的,所以暗示亚马逊在某种程度上对Rust的开发负责。亚马逊雇佣了多名Rust维护者和贡献者,但它只是众多有员工参与的公司之一。Rust库团队负责人Mara Bos的观点却与之相反,她认为Steve的观点“简直是胡说八道”。事情经过一年回头再看原则的这些内容,其实也没有 Steve Klabnik 说的那么离谱,Rust 原则的内容确实对 Rust Project 开发和贡献有指导作用,让大家知道力往哪里使。

今天打算写这篇文章,和读者朋友们一起学习一下 Rust 语言创造过程背后所遵循的原则,进一步理解 Rust 的设计哲学。值得说明的是,Rustacean 原则的大部分详细内容 Niko 并没有写完,所以这里有很多细节内容是由我来补充的。

Rustacean 原则概要

Rustacean 原则主要分为两部分内容:

Rust 语言如何为用户赋能,代表 Rust 和 用户的契约

Rust 社区如何治理才能更贴近 Rustacean ,代表Rust 团队成员和贡献者之间的一种“契约”。

这份原则总的来说,是为 Rust 的总体目标服务的。Rust 的总体目标是:成为一门赋予每个人构建可靠且高效软件能力的语言。

Rust 如何为用户赋能

这部分原则内容包括:

可靠性( Reliable)。如果它编译,它就可以工作。

高性能( Performant)。既高效执行又使用最少内存。

支持性( Supportive)。语言、工具和社区随时为用户提供帮助。

生产力( Productive)。让工作事半功倍。

透明性( Transparent)。让用户可以预测和控制底层细节。

多样性( Versatile)。你可以用 Rust 做任何事。

Niko 所说的 “Rust 之魂”正是指 Rust 团队在这几个关键原则之间的斗争——尤其是生产力、多样性与透明性之间的权衡。

可靠性

具体而言,可靠性意味着要保证安全的 Rust 代码可以避免未定义行为。类型安全是可靠性的关键要素。类型安全不是一种口头建议,而是靠编译器来管理。但是类型安全会增加语言的复杂性,让 Rust 的学习变得更加困难。为此,Rust 团队在错误信息和文档上非常努力,以便减轻这种复杂性带来的学习成本。正是因为这些成本,Rust 团队才对Rust的类型系统试图实现的东西施加了一些限制。

比如,对某些类型的错误条件采用了运行时检查。并不试图证明索引在范围内,而是检查像vec[i]这样的表达式,以确保i < vec.len()。在编译时证明 i< vec.len() 会增加类型系统的复杂性,所以团队选择不这么做,尽管这样会损失一些可靠性,但是增加了生产力。

再比如,允许用户使用 Unsafe 代码逃离类型系统的复杂性。比如,Safe Rust 不能表达双向链表,但可以用 Unsafe Rust 来实现。然而,也希望用户能够封装(安全抽象)他们的 Unsafe 代码,向整个世界展示一个安全的界面。这与 Unsafe 代码作者的生产力感觉相悖(考虑如何封装东西更复杂),但对世界其他地方的可靠性却有很大好处。

另一方面,Rust不隐藏错误条件,并鼓励明确列出所有的可能性(或承认某些东西被忽略)。比如,Rust 要求用户提供详尽的 match 匹配分支,逼迫用户去考虑所有情况。这样做有助于提升 Rust 代码的可靠性,但这是以降低用户的生产力为代价的。所以,这是一个权衡。

错误处理就是一个很好的权衡案例。编程语言历史长久以来,错误处理一般是使用异常。异常处理对用户而言,提升了生产力。但是异常隐藏了控制流,用户很难进行推理,在实践中充满了问题,对可靠性极大的不利。而Rust采用了函数式语言中首创的返回枚举的方法,让用户强制考虑错误处理的方式,这有助于可靠性。其后又引入了?操作符,让用户更方便地传播错误,是生产力的提升,同时确保错误路径对用户来说仍然是可见的,不会被完全忽略。

高性能

Rust 借鉴了 C++ 社区的零成本抽象概念。Cpp 之父 Bjarne 将零成本抽象定义为:“What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better”。零成本抽象意味着,用户可以使用语言提供的高级抽象能力编写代码,而编译器则会通过优化为开发者生成高性能的代码,进一步来说,就是将多余无用的代码优化掉,将有用的代码优化得更加高效。

编程语言

(图片来自于 2021 年Rust Dublin的轻talk: Zero Cost Abstractions[6])

这份性能测试并非要踩 C#/Java 语言,只是为了突出 Rust 的零成本抽象能力的性能。你可以看到,Rust 提供了非常优雅且和Java/C# 等同的高级迭代器抽象,而不会影响代码的性能。

这也是被很多人误会的一点,他们认为 Rust 宣传的零成本抽象是百分之百的。但实际上想要保证百分之百零成本抽象是非常困难的,Rust 在这一方面也充满了权衡。

Rust 的开发者认为,零成本抽象,不仅仅是追求零成本和最佳性能,还更应该着重改善用户体验,因为这就是抽象的意义所在。Rust 语言中的达到这个标准的零成本抽象特性只有少数的几个,这些由 withoutboats 在他的博客中[7]列了出来:

Rust 所有权和借用机制。在没有垃圾收集器的情况下保证内存和线程安全是 Rust 最初的巨大成功故事。

迭代器和闭包 API。这是另一个经典特性。就像上图中所示的迭代器代码一样,你可以优雅地使用各种过滤器、map和for循环,优化出来的代码和手写的高效 C 代码等价。

Async/Await 和 Future。Futures API 是一个重要的例子。早期的 Futures (指 0.3 版本之前)很好地达到了“零成本”的标准,但是没有提供足够好的用户体验。后来通过添加 Pin 来支持跨 await 的引用等,才达到一个用户体验良好的零成本抽象。

Unsafe Rust 和 模块边界(可见性)。这是 Rust 其他零成本抽象之母,因为这是 Safe Rust 的基础。

除此之外的其他特性则没有取得太大的成功,这有些例子:

trait 动态分发,目前没有找到成功的解决方案。

泛型的trait限定,对优化有一定阻碍,所以才引入了 特化(Specialization)。

NewType 模式,在某些情况下优化并不理想(详细可参考延伸阅读reddit相关内容)。

为什么百分百的零成本抽象这么难?因为 Rust 要考虑的因素太多。除了要为用户提供体验良好的抽象之外,还要对透明性和多样性做权衡,这些都是对编译器优化干扰的因素。

很多人对 Rust 的另一个误解就是,用 Rust 实现的代码性能一定很好。但是实践结果很有可能打破他们这层认知。开发者在使用 Rust 代码的时候需要注意考虑以下几个问题:

你的抽象有多少成本

你的代码热点路径在哪(调用频繁的代码),该如何优化

利用好性能基准测试

因为 Rust 零成本抽象并不保证用户写的 Rust 代码性能最佳。

支持性

Rust 工具致力于为开发人员提供优美、流畅的体验。一个例子是编译器如何提供高质量的错误消息,这些消息不仅试图指示错误,而且还教用户 Rust 语言是如何工作的,并就如何修复他们的代码提供有用的建议。最近 Rust 官方还启动了诊断信息多语言翻译计划,欢迎大家去贡献。

对于像 cargo 这样的工具,这体现在精心的 CLI 设计中,使“简单的事情变得简单”。基于 Cargo 的有用的第三方插件变得越来越丰富。

生产力

生产力可能会与可靠性、高性能有冲突,所以这里也存在权衡。可以参考上面列举出来的 Rust 零成本抽象成功的几个特性,比如所有权机制。很多人认为所有权机制影响生产力,是因为这种安全内存管理方式比较新颖,接受起来没有那么快。但是换成 GC 语言,开发者就没有这个心智负担,生产力自然提升。但熟练使用 Rust 的开发者则不会受这个影响。

让 Rust 变得极具生产力的方法是什么?

Rust 致力于跨版本的稳定性。这是因为稳定性是生产力的关键推动因素:如果没有跨版本的稳定性,用户将被迫花时间解决构建失败,而不是构建用户想要构建的功能。但是这种稳定性也会阻碍 Rust 语言开发者们对语言特性设计的自由度,所以引入了 Edition 系统让设计自由度和语言版本稳定性达到平衡。

可移植性。默认情况下,Rust 代码旨在跨所有主流架构移植。

打造繁荣的生态系统。

透明性

Rust 官方团队非常重视透明性。透明性是 Rust 提供给用户的底层掌控力,但需要注意并不意味着它能帮用户自动提升性能。

但透明性暴露的底层控制细节,让多样性和生产力大打折扣。比如 repr属性,再比如异步函数中随处可见的 Box>>。因为它会迫使用户过度关注对当前要解决问题实际并不重要的底层细节。

透明性与多样性、生产力有所冲突,在设计语言特性时需要仔细权衡。

多样性

Rust 同样重视多样性,多样性意味着通用性,意味着 Rust 可以做上层的应用,也可以做底层的系统开发。

官方的目标是以某种方式向 Rust 程序公开所有核心系统功能,即使访问或正确使用它们可能很困难。而不希望 Rust 用户觉得他们必须选择 C 或其他语言,他们应该能够使用 Unsafe Rust 来完成他们的工作。像“内联汇编”这样的功能也遵循这种思路。

我能想到一个比较典型的例子是,Rust 将 Error trait 移动到了 core 中,这样就可以统一 std 和 no_std 的错误处理了。

Rust 社区如何治理才能更贴近 Rustacean

该部分内容提供了以下一些原则,用于帮助 Rust 核心团队和社区贡献者良好合作:

善良体贴。相互尊重彼此才是构建 Rust 未来的基础。

给用户带来快乐。首要目标是让 Rust 用户更有效率和能力。希望人们喜欢使用 Rust,如果他们愿意,也喜欢参与它的社区。

畅所欲言地表达自己。带上你的专业知识,并愿意为你认为正确的事情进行辩论。

认可别人的知识。没有人能垄断好的创意。Rust 团队需要汲取优秀的建议来改进设计。

从小处开始。寻找完美的设计需要迭代。大处着眼,小处着手;当你了解更多时,不要害怕改变。

跟进。说你会做的,做你说的。

把爱传出去。Rust项目成员需要识别有潜力的贡献者,有义务去发展新的成员,并且当好教练的角色。

信任和委托。赋予他人权力意味着愿意让他们以他们认为最好的方式做出决定。

P.S 金发姑娘原则

在 Niko 的博客中还提到一个金发姑娘原则(Goldilocks),比较有趣。

该原则出自一个英国的童话故事《金发姑娘和三只熊》。

讲的是一位金发姑娘偷偷跑进熊的家里,她发现了三碗粥、三把椅子和三张床,粥有冷的、有热的;椅子有硬的、有软的;床有大的、有小的。她都尝了、都试了以后,选择了不冷不热的那碗粥,不硬不软的那把椅子,不大不小的那张床,因为那碗粥、那把椅子、那张床最适合她,对她来说都是“刚刚好”,这种选择的原则就叫做“金发姑娘原则”。

“金发姑娘原则”被应用在各个领域,比如发展心理学、经济学、通讯科学、医学和天体生物学、沟通等等。

如果把该原则用在如何看待(新)事物方面,那么它会成为一个非常好的思维工具。通过该原则,可以让你避免用非黑即白的思维看待这个世界存在的事物,比如 Rust 语言,比如某个人。这个世界并不是好与坏、黑与白这两个极端,它还存在中间状态。

小结

本文可能还缺少很多细节,但总体上我认为应该把 Rustacean 原则和 Niko 所说的 Rust 之魂讲清楚了。Rust 语言并不完美,但它在这些原则之间不断权衡而发展。我们可以不完美,也不可能完美,但不能不追求完美。感谢阅读。

审核编辑 :李倩

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分