对 Rust 10 年的押注以及我对未来的期待

2015年6月,Rust 1.0登陆的喧嚣一个月后,我写下了第一行Rust代码。我来自 C、Python 和 JavaScript,从此一发不可收拾。两家基于 Rust 的初创公司和 50 万行 Rust 代码之后,我在此对这一里程碑进行了一些反思。

早期是痛苦的

版本兼容性很差–无论是 Crate 之间还是编译器本身。错误修复更新可能会迫使编译器升级,这反过来又会拖入不稳定的 crate,如 syntexonce 是一个关键的serde依赖项)及其依赖项。在实践中,我们会 “更新世界”,并在需要修复重要错误或功能时进行时间上的固定–早期的情况是每 6 周发布一次编译器。我们浪费了大量时间二进制搜索兼容的版本组合。元素周期表

对我来说,“与借用检查器斗争 ”是真实存在的。有了 C++、Java 和 Objective-C 的背景,Trait 自然而然就会出现,但生命周期和(*mmostly)不影响代码生成的 “证明 ”想法却需要一段时间来摸索。耐心的朋友和同事让这一切变得更容易!

经过几年的发展,我们的代码库和团队不断壮大,编译时间变得非常痛苦。大类型是一个反复出现的问题(现在仍然是,偶尔),需要诊断和缓解。投资降低了我们的成本,但迭代周期仍然受到影响,快速原型开发通常需要花费大量精力来建立。

我们的员工过去和现在都非常出色

Rust 生态系统拥有令人印象深刻的编程 “品味”,体现在依赖关系中的是相对简单的构建、优雅的实现以及快速而稳健的性能。相对而言,使用 TypeScript 或 Python 会让人感到沮丧。Rust 连续九年获得“最受喜爱/推崇的语言”称号是有原因的!

我认为,“正确之处 ”本身就是一篇文章,但我认为,不断发展的、有奉献精神的、有主见的、认真的志愿者队伍,以及强烈的说 “不 ”和 “还不行 ”的观念,才是关键所在。

作为雇主,我从中受益匪浅。我们有幸成为伦敦为数不多的 Rust 工作机会之一,拥有一批热衷于使用自己喜爱的语言工作的优秀工程师。普通的 Rust 程序员比普通的程序员更优秀,这也是一个额外的收获。

Rust 已成为一种安全的选择(在某些领域)

早期需要大量的牦牛剃毛。例如,“std ”中的遗漏导致我们逐渐形成了一个变通方法、黑客和扩展库:这些代码简化或优化了我们的应用代码,但尚未准备好或没有理由向上游合并。随着时间的推移,向该库添加代码的速度有所放缓,我们发现自己现在经常从该库中删除代码。我们的 自定义 实现 消失!如今,能够依赖std的基元,哪怕是相对不常见抽象的实现,并对它们进行良好的优化,也是一件乐事。

这种可靠性现在已经扩展到 Rust 的大部分体验中。构建和升级的可预测性大大提高,非 Rust 的依赖性减少,编译时间/codegen/内联崩溃的情况不再出现,生态系统对夜间特性的依赖性降低,对 semver 的尊重程度提高。推理 改进 使借用检查程序对新人更加友好。夜间 ICE 也少了很多!以前,我们几乎每周都能看到新的 ICE 或链接错误,现在最多每季度一次。

Crate 生态系统的可预测性也更强:jiffpolarstauri等较新的 Crate 建立在早期项目来之不易的经验教训之上,而 tokiohyperregex等中坚力量则通过多年的大量生产使用赢得了稳健性。

十年前,在生产中选择 Rust 会让你重新发明轮子,并绕过已知和未知的问题。虽然在某些领域,比如我们在浏览器中使用 Rust,但对于一般系统和后端工程来说,这已经是过去式了。Rust 让我们能够专注于业务逻辑,同时开发出快速而强大的应用程序。

今天的 Rust 让人感觉编程应该是这样的

在大型软件项目中,Rust 不仅是一个安全的赌注,它还拥有前所未有的程序员同理心:简单而强大的构建、最好的错误信息和内核、优秀的文档和集成开发环境集成、强大的CI回归测试。Rust 给人的感觉就像是一个充满激情的项目,是程序员为之付出的爱的结晶。

10 年前我们能预测到这一点吗?我想有些人可以。一些人对现状感到痛苦,他们看到了一种语言、一个生态系统的潜力,这种语言、生态系统是由像他们这样的人精心设计的。他们自愿贡献出自己的时间,成就了现在的 Rust。正如 Rust 的原作者之一 Graydon Hoare 所说

今天的 Rust 是前瞻性机构大量投资的结果,也是成千上万个人共同努力的结果。

10 年前,我把时间和(我投资者的)资金都押在了 Rust 上,因为我相信这种回报。热情被感染,潜力可触可感,聪明的头脑汇聚在一起。我很高兴这一切只增不减,也非常感谢为此做出贡献每一个人

未来十年我的期待

更简单、更快速的构建

随着工程和测试带宽的不断扩大,我们可以继续用更简单、更快速的依赖关系取代经过实战检验但复杂或缓慢的依赖关系。我直接或间接受益于链接回溯平台优化例程TLSHTTPgit压缩构建切换工具链,以及构建自动化。我特别期待得到更多关注的几个工具是纯 Rust不那么特殊 std、减少对系统链接器的依赖、纯 Rust 加密持久的 BTreeMap和 Rust 游戏引擎。后者不是给我用的,而是因为游戏开发人员带来了一些最激进的优化 我很想从中受益。

在性能方面投入了大量的专业知识和精力。在过去的几个月里,Tably 的前端和后端编译时间提高了 60%。许多最大的成功来自于图表中看不到的元变化,包括ThinLTO 和后端并行增量元数据管道化前端并行。可能取得进一步成果的元变化有 面向 Rust 的代码生成、增量链接器消除死代码新的求解器cargo test 缓存结果

改进了可移植性,减少了 #[cfg()]

在 CI 上测试有效的 #[cfg()]选项、目标和功能的强大集合通常是令人望而却步的。这会产生未经测试的代码路径和潜在的编译失败,以及诸如零散和不完整的文档、IDE 功能受损和 unused_imports 警告等问题。

有人建议使用 lint来缓解这一问题。一个酝酿中的更好办法是#[cfg()]移入 Trait 系统 (cc) where Platform: Unixwhere Platform: Avx2。这样,编译器自然就能保证在所有支持的选项、目标和特性下进行编译。通过specialization,可以对具有特定操作系统或特定架构实现的项目进行专门化。元数据可以在 dev/test/release、不同特性组合和不同平台构建之间共享,从而减少重新编译,加快 CI,并使 crates.io MIR cache 更接近现实。

一切都在 “恒定 ”之中

恒定评估减少了对宏和构建脚本的依赖,并将运行时的工作和恐慌提前到编译时。目前,我们对此只是浅尝辄止,因为只有一小部分 Rust 代码受支持(无 Trait、无分配),而且受支持的代码对于非复杂用例来说运行太慢

假设可移植,所有代码都可以在编译时进行评估(cc)。像 FFI 或汇编这样没有通用回退机制的不可移植代码越来越少了。受各种 效应的启发,我们已经做出撤销再次尝试了无处不在的 const。我希望我们能以 “一切都可以在const上下文中执行 ”这一简单假设来避免这种额外的语言复杂性。虽然有棘手 问题需要克服,但语言的简洁性是值得的。

更简单的并发

由于'static boundcc)、cancellation-safety以及与traitdyn有关的限制async对我们来说具有相对较高的复杂性代价。这些问题目前似乎 无法解决同步异步基元之间的分叉以及生态系统特质进一步增加了theasynctax效果启发 解决方案似乎并不乐观

1.0 之前的 Rust 有一个解决方案: libgreen。它在用户空间中实现了并发,而没有出现这种分叉,但却付出了巨大的性能、可移植性和维护成本,因此被移除。随着工程带宽的增加,这可能值得重新考虑。同样,语言的简洁性也是值得的。(总有一天,我会使用 生成器创建一个零成本封装 std::{fs, net} 加上 fiber::{spawn, select} 的 PoC!)

在更多领域大显身手

浏览器中的 Rust 感觉还未被充分开发:我们遇到的问题表明 没有 很多 其他 使用 认真。像避免 UB和获取跨浏览器堆栈跟踪这样的桌面赌注花费了我们的时间和精力。leptos–本网站背后的网络框架–已经取得了长足的进步(就像我们之前选择的sycamore一样),但粗糙的边缘依然存在。

某些领域,如快速原型开发和业务逻辑,由于设计上有意牺牲迭代速度,Rust 可能仍然无法胜任。但对于大多数领域来说,我认为这只是时间问题。图形用户界面、机器学习和游戏开发由于生态系统根深蒂固、转换成本高、文化惰性和工具成熟等原因,在采用上仍然存在障碍。但这些障碍并不是永久性的。得益于社区的不懈努力和创新,Rust 将继续走向成熟,并稳步进入这些领域。

结论

展望未来,Rust 的发展轨迹清晰而令人振奋。采用 Rust 会促进工程和测试带宽的增长,而工程和测试带宽的增长又会进一步推动 Rust 的采用,这种正反馈循环正在加速。在即将到来的十年中,我们将进一步完善技术,加快编译速度,扩大应用领域,为开发人员提供更加完美的体验。在 Tably,我们将继续致力于推动 Rust 的发展,为见证语言的下一个篇章并为之做出贡献而感到兴奋。为 Rust 的下一个十年干杯!

本文文字及图片出自 10 years oitting on Rust and what I’m looking forward to next.

你也许感兴趣的:

共有 48 条讨论

  1. 我认为有些人不在乎博客背后的技术,而是专注于写作,这很好,但这简直是太过分了。我一直在无意中编辑这篇博客文章,而似乎没有简单的方法来阻止它发生……

    编辑:天啊,这是你的产品!?请别这样,这只是个愚蠢的花招。在X11中,中键点击会粘贴选中内容,这是我工作效率的关键功能,而现在当我尝试在新标签页打开链接时,却会不小心粘贴选中的文本。

    1. 我原本也以为这是个花招,直到我访问了他们的落地页,发现那也是个可编辑的文档。这是故意设计的。

    2. > 天啊,这是你们的产品!?请别开玩笑了,这简直是个愚蠢的花招。在X11中,中键点击会粘贴选中内容,这是我工作效率的关键功能,而现在当我尝试在新标签页中打开链接时,却会粘贴我选中的文本。

      确实,应该有一个不可编辑模式。

      1. 把它變成付費功能,你就會富得流油!

    3. 這稍微可以原諒,因為至少文字應該可以編輯,但 Azure 票券也有這個中鍵問題。這太令人抓狂了。

  2. 這是一篇非常積極的文章,與我的經驗相符,唯一的黯淡前景是:

    “由于‘静态绑定(cc)、取消安全性以及与特性和 dyn 相关的限制,async 对我们来说具有相对较高的复杂性成本。这些问题目前似乎无法解决。同步和异步原语之间的分叉以及生态系统的特殊性进一步增加了异步成本。受效果启发的解决方案似乎并不乐观。”

    “1.0 之前的 Rust 有一个解决方案:libgreen。它在用户空间实现了并发,而无需这种分裂,但付出了显著的性能、可移植性和维护成本,因此被移除。随着工程带宽的增加,这可能值得重新考虑。再次,语言的简洁性是值得的。(有一天,我将创建一个使用生成器的 std::{fs, net} 加上 fiber::{spawn, select} 的零成本封装的 PoC!)”

    1. > “由于静态绑定(cc),async 对我们来说复杂度成本相对较高。”

      我可能有误,但我认为这是 Tokio async 运行时的设计选择,而不是 Rust 的设计选择。例如,Embassy async 运行时没有这个绑定,但你必须自己处理固定。此外,静态绑定应该降低复杂度成本,而不是提高它。

  3. 作为 2022 年底深入研究 Rust 的人,我总是觉得阅读这些经历过 2015 年艰难时期的人的故事非常有趣。我很幸运能在语言发展得更成熟的时候学习它,因为这无疑让原本就非常陡峭的学习曲线变得更轻松一些。

    从某种程度上来说,我觉得自己正在经历文章中描述的情况,只是对象是 Zig。我猜 Zig 现在的情况与当时的 Rust 差不多。尽管如此,我仍然非常喜欢使用它。

    1. 这里有一种强烈的“让事情比你发现时更好”的文化。当用户对工具或语言感到困惑时,这不是用户的错。如果你感到困惑,很可能其他人也会感到困惑,因此在遇到问题时进行修复或改进,其好处是无穷无尽的。正如俗语所说,种树的最佳时机就是今天。

      这样做的好处之一是,如果有人尝试过但失败了,一年后再次尝试时,他们的体验可能会得到足够的改善,不会再次失败。有一段时间,我认为我能给想要学习 Rust 的人最好的建议是“等 6 个月” 🙂

      1. 这是非常好的态度。我知道大语言模型(LLMs)正在成为新的戈德温定律,但我真的希望这种态度能够在大语言模型(LLMs)工具中普及。我们所能做的只是“如果不起作用,说明你的提示错了”。但我认为“等 6 个月”仍然适用(除非你是“NGMI”)。

      2. 我非常同意这一点,这也是我继续使用Zig的原因之一。我会尽量反馈那些对我来说不太明显的问题。

    2. 每当编程语言被大型科技公司(如微软、谷歌等)或大型开源项目(如Linux等)采用时,都已证明其生态系统已成熟。

      但我无法对Zig做出同样的评价,因为我认为它与现有技术相比并非重大更新。

      1. 对我来说,Zig的价值在于其作为语言的简洁性(与Go的简洁性有相似之处),以及对内存分配的显式且精细控制。除此之外,我非常欣赏带标签的联合体和错误处理。它为 C 语言等语言增添了足够的价值,让我觉得它值得使用。

        尽管如此,无论何时需要绝对的代码正确性和生产级质量,我都会使用我最喜欢的语言 Rust,尽管这需要编写更多的代码,花费更多的时间来“正确”地完成工作。

        1. > Zig 的价值在于其简单性

          依我之见,Zig 有些地方根本不在乎,这在表面上让它显得简单,但后来却变得令人烦恼和繁琐。

            * 手动实现接口
            * 结构体的内部是公开的
            * 没有字符串类型
            * anytype 需要阅读代码
            * 错误无法携带信息
            * 标准库中的迭代抽象到处都是
           * 未使用的声明会导致编译错误
          1. 我对接口和字符串类型的看法与你一致。至少这两者在最简单的程序中都非常普遍,值得语言和标准库提供适当的支持。

        2. > 对我来说,Zig 的价值在于其作为语言的简洁性

          我昨天读了一篇关于 Rust 的文章,然后我意识到:我完全不知道这段代码在做什么(而且我猜“templage”是个拼写错误):

              #[derive(Debug, Default)]
              pub struct Context {
                  authed: bool,
              }
              #[derive(Template)]
              #[templage = “layout.jinja”]
              struct HomeTemplate {
                  ctx: Context
              };
              pub async fn home() -> impl 
              IntoResponse {
                  HtmlTemplate(
                      HomeTemplate { ctx: 
              Context::default() }
                  ).into_response()
              }
          1. 看起来这是一个使用Jinja模板引擎编写页面的HTTP框架的小型请求处理器。我有点生疏(双关语),所以无法立即确定这是哪个HTTP框架。

            1. 你说的对,这个HTTP框架看起来像是Axum。

            2. 我的意思是,我明白那部分:)。我猜这个异步函数可能是某种工厂?为什么需要一个异步工厂,我完全不明白。

          2. 是的,虽然宏简化了一些任务,但它们也让真正理解代码变得更难。从技术上讲,它们在大部分语言中都隐藏了代码,因为它们是使用另一种语言进行的元编程。

            我喜欢 Rust,有时也会编写一些高级代码,但很少创建宏。我仍然认为宏是一种“超人”库开发人员为了让自己的生活更轻松而掌握的“魔法”。通常情况下,我不会使用宏。

            这是 Zig 的另一个优势。如你所知,Zig 中的元编程并不使用任何特殊语法,而是直接使用 Zig 语言本身。这通常被称为“编译时”(comptime),虽然理解起来可能并不容易,因为你需要考虑编译时的事物,但至少它是用纯粹的 Zig 语言实现的。

            1. 公平地说,你可以使用一个函数在 Zig 中生成一个类型,但结果会产生一些晦涩难懂的方法等。不过,我认为 Zig 开发人员会回避这种模式。

          3. 我个人认为,一旦涉及宏,rust API 的发现就变得非常困难。我们无法知道宏会接受什么参数。例如,“简单的” `println!`,如果没有文档字符串中的示例,即使我们有显示实现的 IDE,也无法猜测参数是什么。

          1. 粗略浏览后,我产生了相反的反应,文章的措辞非常笨拙,这在大语言模型(LLMs)中从未见过,对我来说非常人性化。

            比如

            > 此外,Zig 的学习曲线可能比较陡峭,对于那些不熟悉低级编程概念的人来说尤其如此。

            我认为大语言模型不会用“此外”来开头

            > 小而简单的语法是维护的重要组成部分,因为该语言的目标是让维护人员无需学习他们可能不熟悉的语言的复杂性,就能对代码进行调试。

            这里的措辞感觉像是 Zig 的粉丝将赞扬 Zig 设计的句子改写成更中性的百科全书风格。

            1. > 评论者提到的其他挑战是与其他语言的互操作性……

              这听起来就像大语言模型(LLMs)对我进行比较时说的话。什么评论者?

  4. 不小心点击任何内容导致文章的意外本地删除和修改非常令人烦恼。可能是技能问题吧?

    1. 我花了一段时间才弄清楚你的意思,但显然这篇博客文章托管在Tably [1]上,允许用户本地编辑页面。人们会期望在公共帖子中禁用这些功能的设置。

      [1] https://tably.com/

      1. 谢谢,我正试图弄清楚这是什么奇怪的AI垃圾信息

    2. 已知晓,正在处理中!页面采用写时复制机制,因为网站上的大多数页面本意是作为模板使用,这种行为在这种情况下是有意义的。正在努力解决如这些帖子中提到的烦人问题。长期存在的浏览器特定问题,如https://bugzilla.mozilla.org/show_bug.cgi?id=1001790,使得这个问题变得棘手!

      1. 不,这完全没有必要(看看评论就知道了)。

        > 长期存在的浏览器特定问题……让这个问题变得棘手!

        我认为这不是事实,没有浏览器特定问题会阻碍人们抱怨的任何事情。或者你在某个浏览器中有一个PoC?

      2. 你能让那些用来链接到其他网站的按钮看起来不像链接吗?当我看到一个蓝色下划线的单词,鼠标悬停时会高亮,但无法用中键点击时,这让人很困惑。

      3. 博客文章真的需要可编辑吗?绝大多数查看页面的人只是在阅读文章,而不是编辑它。

      4. 顺便问一下,你能让链接变成a吗?

    3. 让我感到烦人的是,他们的链接不知怎么搞的破坏了中键功能,所以每次我想点击一个链接时,都得花几秒钟时间回忆他们网站具体是如何实现的。

    4. 这是为了警示大家关于不必要可变性的风险。

  5. 我删除了帖子中的一段内容。这出乎意料。不过没人真的在意大写字母吧?

    我想如果我发现有什么内容我真的不同意,我可以修改它,所以这算是个额外好处。

    1. 我重新撰写了内容,使其与Go相关

  6. 我觉得 Rust 促进了函数式编程的发展。我创建了一个在前进时会改变自身状态的解析器,但可变性和借用性使这种做法难以实现,因此我将其改为无状态解析器,它必须返回一个索引,而不是调整内部索引。人们经常遇到这样的问题吗?传统模式不起作用,必须采用完全不同的方法来使用 Rust?

    1. 我也有过类似经历。我认为这很大程度上取决于当前的复杂度。例如,如果问题相对简单,即使采用大量变异和命令式风格,也能通过跟踪变量避免陷阱。但当复杂度增加时,我倾向于采用更函数式的风格并尽可能避免变异,因为这显然能避免许多问题。

      我认为 Rust 的借用检查器和生命周期使得使用传统模式变得更加困难,更倾向于采用函数式方法。显然,对于不习惯函数式编程的人来说,有时采用函数式方法可能会比较困难,但我认为这可以让编译器更满意。

  7. Async/await 是阻止我使用 Rust 的唯一因素。

    1. 它应该是吸引你使用 Rust 的主要因素之一,因为它简化了许多典型的并发模式。

      当我刚开始使用时,我觉得它很糟糕,好像一种感染,最终一切都必须异步,但那是因为我不了解如何与异步代码交互。使用 spawn、spawn_blocking 和 futures::stream 占了我用例的 90%,它使设置“屏障”变得很容易,越过这个屏障,async 就无需传播了。

      1. 我做过一些 Rust(只是单线程,几年前的事了),但听起来像依赖倒置原则又出现了。

        DIP、async/await、彩色函数、IO单子。所有这些场景中,业务逻辑都应放在一列(抽象/同步/蓝色/纯粹),而你的实现机制应放在另一列(具体/异步/红色/IO)。这样你的逻辑将始终可进行单元测试,无论其规模大小。

        感到沮丧的新手会抱怨无法反向调用。那么语言设计师该如何抉择?是通过不阻碍用户来吸引尽可能多的用户群体,还是过滤掉沮丧的用户,从而拥有数量较少但质量更高的代码库?

        1. 依赖倒置原则确实有时适用,但一个设计良好的库会将异步操作封装在需要异步处理的函数中——例如与文件系统或网络交互的操作等。这些操作本身是完全可测试的。你的核心业务逻辑仍然可以保持同步,如果你愿意的话。异步可以正常调用同步,同步也可以调用异步,只要你愿意承认在单元测试中你正在抽象化一个网络调用(出于某种原因)。

          我喜欢的典型模式是:

          * 同步用于逻辑

          * 异步用于任务运行器,因为我想在任务运行时做其他事情。

          * 根据需要使用同步或异步进行数据处理(数据库调用、网络、文件系统)

          因此,你基本上拥有:

          1. 通过调用异步函数启动任务,返回一个未来对象。

          2. 任务主要进行异步数据处理,随后是一个经过单元测试的同步逻辑块,最后是异步“保存”操作(如需)。

          3. 您可以选择等待未来对象完成,或在后台运行任务,或在异步块中使用`.await`。

          我这里省略了具体语法细节,但这就是核心思路。99%的代码并非异步,而那些异步的代码也绝非为了异步而异步。

    2. 我理解你的想法。但对我来说,这是我使用 Rust 的主要原因之一,因为克服了最初的障碍后,我突然就明白了。与普遍看法相反,我喜欢这种语法,也不太在意函数的颜色。

      尤其是在使用tokio时,我感觉我的解决方案效果不错。我也很欣赏他们为几乎所有值得使用的std函数创建了异步等效函数。也许这会造成分歧,但我的并发程序感觉很容易编写,性能也相当不错。

      我唯一有些问题的是任务取消之类的事情,但我认为这是我自己的技能问题,而不是tokio或Rust中async/await的实现方式的问题。

      1. 它已经有了,而且已经相当成熟且被广泛使用。有些用户出于哲学原因反对使用 async/await,所以我认为这就是用户这么说的原因……另一种可能是用户不喜欢它的实现方式,与其他语言相比。

  8. 1. 这个网站以一种方式显示链接,导致浏览器在窗口底部不显示链接的目标。

    2. 我尝试通过在地址栏输入新 URL 来重新打开标签页,但无效,浏览器会重新打开同一页面并替换回原 URL!

    这是什么情况?!

  9. 你的链接不支持中键点击。哦……等等,这像是某种实时编辑视图?什么?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注