“
也许这篇文章可以消除你对 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 语言并不完美,但它在这些原则之间不断权衡而发展。我们可以不完美,也不可能完美,但不能不追求完美。感谢阅读。
审核编辑 :李倩
-
编程语言
+关注
关注
10文章
1944浏览量
34726 -
Rust
+关注
关注
1文章
228浏览量
6605
原文标题:Rustacean 原则 与 Rust 之魂
文章出处:【微信号:Rust语言中文社区,微信公众号:Rust语言中文社区】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论