JavaScript转Rust:三年的转变值得吗?

发表时间: 2024-06-12 13:48

几年前,我放弃了一切,100%专注于WebAssembly。当时,Rust对编译到WebAssembly方面有最好的支持,而功能最齐全的WebAssembly运行时是基于Rust的。Rust是菜单上最好的选择。我跳了进去,迫不及待地想看看所有的炒作是关于什么的。

从那时起,我(和其他一些很棒的人)构建了Wick,这是一个使用WebAssembly作为其核心模块系统的应用程序框架和运行时。

三年后,在crates.io上部署了多个生产部署、一本电子书和~100个软件包,我觉得是时候分享一些关于Rust的想法了。

你可以用更少的来维持更多

我是测试驱动开发的忠实支持者。我习惯了用Java和JavaScript等语言进行测试。我开始用Rust编写测试,就像用任何其他语言一样,但发现我正在编写不会失败的测试。一旦你到了测试可以运行的地步——也就是说,你的Rust代码编译的地方——Rust已经解释了如此多的错误,以至于许多常见的测试用例变得无关紧要。如果您避免unsafe {}块和容易恐慌的方法,如.unwrap()您将从默认避开许多问题的基础开始。

Rust的借贷检查器的侵略性,Rust的类型系统的丰富性,功能模式和库,以及缺乏“空”值,都导致在测试等地方花费更少的精力来维护更多。我在Wick项目中维护了70,000多行代码,测试比我在其他语言中需要的测试要少得多。

当您需要编写测试时,无需考虑即可在飞行中添加它们。Rust的集成测试线束让您几乎不用考虑就在代码旁边添加测试。

我现在用其他语言编码得更好

在Rust中编程就像处于情感虐待关系中。Rust整天对你大喊大叫,经常谈论你在另一种生活中会认为完全正常的事情。最终,你习惯了发脾气。它们成为例行公事。你学会走钢丝,以避免触发编译器的脾气。就像在现实生活中一样,这些行为变化会永远与你同在。

情感虐待通常不被认为是鼓励改变的健康方式,但它确实影响了变化。

当行不按顺序或返回值未选中时,我不能用其他语言编写代码而不感到不舒服。当我遇到运行时错误时,我现在也会感到非理性的沮丧。

你说“done"不是函数是什么意思?你为什么不让我知道“done”可能不是一个功能??

Clippy太棒了!

Clippy是Rust的linter,但称它为linter是一种伤害。在一种编译器可以让你哭泣的语言中,Clippy更像是一个温柔的朋友,而不是一个linter。

Rust标准库是巨大的。当这么多功能分布在无数的粒度类型、特征、宏和函数中时,很难找到您知道可能存在的函数。许多Clippy规则(例如,manual_is_ascii_check)寻找stdlib方法或类型更好替换的常见模式。

Clippy有数百条规则来解决性能、可读性和不必要的间接性。在可能的情况下,它会经常为您提供替换代码。

看起来(很快)您终于能够为项目配置全局林特了。到目前为止,你必须破解你的解决方案,以保持项目的绒点一致。在Wick中,我们使用脚本自动更新几十个板条箱的内联绒毛配置。Rust社区花了数年时间才找到解决方案,这让我们......

坏的

有些差距你将不得不接受

每次我回到上面的Clippy问题时,我都会质疑自己的理智。当然,我错了。一定有一个我错过的配置。我简直不敢相信。我还是做不到。必须有一种方法可以全局配置绒毣。当我写这篇文章时,我检查了四次,以确保我没有妄想。这些问题现在已经关闭了,但它们已经开放多年了。

Clippy很棒,但这个用例是Rust世界许多人的一个例子。我经常遇到不涵盖我用例的库或工具。这在较新的语言或项目中并不少见。软件需要时间(使用)才能成熟。但Rust不是那么新。Rust 有一些感觉不同。

在开源中,边缘案例经常由早期采用者和新用户解决。他们是那些有边缘外壳的人。他们的公关完善了项目,以便更好地为下一个用户提供。Rust在十年的大部分时间里被评为“最受欢迎的语言”。吸引新用户没有问题,但它并没有导致库或工具的大幅改进。它导致处理特定用例的一次性叉子。我也为此感到内疚,但不是因为缺乏试图获得公关。

我不知道为什么。也许维护稳定API的压力,以及Rust的粒度类型系统,使库所有者难以进行。如果小幅更改会导致重大版本颠簸,那么很难接受。

或者可能是因为编写为每个人做一切的Rust代码非常困难,人们不想处理它。

Cargo、crates.io以及如何构建项目

我围绕我看到的其他一些热门项目建模了Wick存储库结构。它看起来很合理,而且工作正常,直到它没有。

您可以使用Cargo轻松构建、测试和使用感觉像模块大小的板条箱。不过,将其部署到crates.io?那是一个完全不同的故事。

除非每个引用的板条箱也单独发布,否则您无法将软件包发布到crates.io。这有点道理。您不想依赖仅存在于作者本地文件系统上的包的板条箱。

然而,许多开发人员自然地将大型项目分解为较小的模块,您无法发布仅存在于自身内部的子板条箱的父板条箱您甚至不能发布具有本地开发依赖项的板条箱。您必须在发布随机公用事业箱或重组项目之间做出选择,以避免这个问题。这种限制感觉是任意和不必要的。你可以清楚地构建这样结构的项目,你只是不能发布它们。

编辑:Ed Page联系了,指出您可以使用本地开发依赖项进行发布,只要您不包含一个versionCargo.toml

不过,货物确实有出色的工作空间支持!与大多数语言相比,Cargo的工作空间提供了更好的管理大型项目的体验。但他们没有解决部署问题。事实证明,您可以通过十几种方式中的任何一种方式设置工作区,但都没有方法易于部署。

您可以在旨在简化发布工作空间的实用工具箱数量中看到问题。每个都使用一个配置的子集,我仍然无法避免设置工作区的“一种真正方法”。当我发布Wick时,经常需要一个多小时的努力,将手动、重复的任务与仅部分工作的工具相结合。

异步

Rust在语言开始后增加了异步性。这感觉就像事后的想法,就像事后的想法,经常会因为难以理解和解决的错误而妨碍你。当您搜索解决方案时,您必须根据各种运行时及其异步风味进行过滤。想使用异步库吗?您有可能无法在特定的异步运行时之外使用它。

经过二十年的JavaScript和良好的Go经验,这是与Rust的挫折和摩擦的最重要来源。这不是一个不可逾越的问题,但当异步怪物抬起头时,你必须随时准备好应对它。在其他语言中,异步几乎是看不见的。

丑陋的

重构可能很搓不倒

Rust的丰富类型系统是一种祝福和诅咒。在Rust类型中思考是一个梦想。管理Rust的类型可能是一场噩梦。您的数据和函数签名可以具有通用类型、通用生命周期和特征约束。这些约束可以有自己的通用类型和生命周期。有时,您将拥有比实际代码更多的类型约束。

大于逻辑的约束

您还需要在每个impl上定义所有泛型。第一次写的时候很乏味。然而,在重构时,它可以将一个小变化变成级联混乱。

简单的通用ID被一遍又一遍地复制。

当您需要调整14个不同的定义才能向前迈出一步时,很难取得快速进展。

编辑以解决外部评论:问题不在于可表达性,问题在于没有语言或工具解决方案来减少重复。经常有理由有相同的约束或引用相同的通用列表,但没有办法别名或以其他方式引用中心定义。我不确定是否应该有,但这并没有改变重复的负担。

判决

我爱Rust。我喜欢它能做什么,以及它的多才多艺。我可以用与CLI应用程序、Web服务器Web客户端相同的语言编写系统级代码。使用WebAssembly,我可以使用与命令行相同的二进制文件在浏览器中运行LLM。这仍然让我大吃一惊。

我喜欢坚如磐石的Rust程序。在你学会欣赏Rust保护你免受什么影响后,很难回到其他语言。我回到Go短暂的一段时间。我很快就又被发展的速度陶醉了。然后我遇到了运行时的恐慌,玻璃碎了。

但Rust有它的疣。很难雇佣,学习缓慢,而且太僵硬,无法快速进行。很难排除内存和性能问题,特别是异步代码。并非所有库都像其他库一样擅长安全代码,开发工具还有很多需要改进的地方。你开始落后了,有很多对你不利的事情。如果你能越过障碍,你会让每个人都在尘土中。那是一个很大的如果。

Rust对我们来说值得吗?现在说还为时过早。我们和一个小团队一起做了令人惊叹的事情,但也有巨大的障碍。我们也有技术原因使Rust更可行。

这对你来说值得吗?如果你需要快速进行Iterate,可能不需要。如果您有已知的范围,或者可以吸收更多的前期成本?一定要考虑一下。你最终会使用防弹软件。随着WebAssembly的角度每个月变得越来越强大,编写一次完美的软件并在任何地方重复使用它的前景正在尽快成为现实。