在初创公司使用Rust的警示故事 - ndw

22-11-24 banq

我在Rust方面的主要经验来自于在以前的一家创业公司工作了2年多一点的时间。这个项目是一个基于云的SaaS产品,或多或少是一个传统的CRUD应用:它是一组微服务,在数据库前提供一个REST和gRPC API终端,以及其他一些后端微服务(本身是用Rust和Python组合实现的)。使用Rust主要是因为公司的几个创始人是Rust专家。随着时间的推移,我们的团队得到了很大的发展(工程人员增加了近10倍),代码库的规模和复杂性也随之大大增加。

随着团队和代码库的增长,我感觉到,随着时间的推移,我们为继续使用Rust付出了越来越沉重的代价。开发有时很缓慢,推出新功能所需的时间比我预期的要长,而且团队从早期使用Rust的决定中感受到了真正的生产力下降。从长远来看,用另一种语言重写代码会使开发更加灵活,并加快交付时间,但要找到时间进行重大的重写工作是非常困难的。所以我们只能用Rust,除非我们决定咬紧牙关重写大量的代码。

Rust有一个巨大的学习曲线
在我的职业生涯中,我使用过几十种语言,除了少数例外,大多数现代程序性语言(C++、Go、Python、Java等)在其基本概念方面都非常相似。每种语言都有其不同之处,但通常只需学习一些不同语言的关键模式,然后就能很快提高工作效率。然而,对于Rust,人们需要学习全新的概念--像生命期、所有权和借用检查器。对于大多数使用其他普通语言的人来说,这些概念并不熟悉,即使对于有经验的程序员来说,也有一个相当陡峭的学习曲线。

当然,其中一些 "新 "想法在其他语言中也有--尤其是函数式语言--但Rust将它们带入了 "主流 "语言环境,因此对许多Rust新手来说是新的。

尽管他们是我工作过的最聪明和最有经验的开发者,但团队中的许多人(包括我自己)都在努力理解在Rust中做某些事情的典型方法,如何摸索来自编译器的经常是神秘的错误信息,或者如何理解关键库的工作原理(下面有更多关于这个的内容)。我们开始为团队举办每周一次的 "学习Rust "会议,以帮助分享知识和专长。这对团队的生产力和士气都是一个很大的消耗,因为每个人都感觉到了开发速度的缓慢。

作为一个软件团队采用新语言的比较点,我在谷歌的一个团队是第一批完全从C++转向Go的团队之一,在整个15人左右的团队第一次相当舒适地用Go编码之前,只花了两个星期。对于Rust,即使每天用该语言工作了几个月,团队中的大多数人也从未感到完全胜任。许多开发人员告诉我,他们经常感到尴尬,因为他们的功能落地所需的时间比他们预期的要长,而且他们花了这么长的时间来思考Rust的问题。

还有其他的方法来解决Rust所要解决的问题
如上所述,我们正在构建的服务是一个相当简单的CRUD应用。在这个特定系统的生命周期中,这个服务的预期负载将不超过每秒几个查询。该服务是一个相当复杂的数据处理管道的前端,可能需要许多小时才能运行,所以该服务本身预计不会成为性能瓶颈。我们并不特别担心像Python这样的传统语言会在提供良好的性能方面遇到任何问题。除了任何面向网络的服务需要处理的问题之外,没有特别的安全或并发需求。我们使用Rust的唯一原因是该系统的原始作者是Rust专家,而不是因为它特别适合于建立这种服务。

使用Rust是已经做出决定,安全比开发者的生产力更重要。
这在很多情况下是正确的权衡--比如在操作系统内核中构建代码,或者用于内存受限的嵌入式系统--但我不认为这在所有情况下都是正确的权衡,尤其是在速度至关重要的初创公司。
我是一个实用主义者。我宁愿让我的团队花时间去调试偶尔出现的内存泄漏或用Python或Go编写的代码的类型错误,也不愿让团队中的每个人因为使用一种旨在完全避免这些问题的语言而遭受4倍的生产力打击。

正如我在上面提到的,我在谷歌的团队建立了一个完全用Go编写的服务,随着时间的推移,它已经支持了超过8亿的用户,并且在高峰期的QPS大约是谷歌搜索的4倍。在建立和运行这项服务的几年里,我可以用一只手数出我们遇到由Go的类型系统或垃圾收集器引起的问题的次数。
基本上,Rust旨在避免的问题可以通过其他方式解决--通过良好的测试、良好的提示、良好的代码审查和良好的监控。当然,并不是所有的软件项目都有这样的奢侈,所以我可以想象,在那些其他情况下,Rust可能是一个不错的选择。

你将很难聘请到Rust开发人员
在我在这家公司工作期间,我们雇佣了一大批人,但在加入工程团队的60多人中,只有两三个人以前有Rust的经验。这并不是因为我们想找到Rust开发人员--他们只是不在那里。(同样,我们也不愿意雇佣那些只想用Rust编码的人,因为我认为在一个需要以敏捷的方式选择语言和其他技术的创业公司里,这是一个不好的期望)。随着Rust成为主流,这种Rust开发人才的匮乏将随着时间的推移而改变,但围绕Rust建立的假设是你能够雇用已经知道它的人,这似乎是有风险的。

另一个次要因素是,使用Rust几乎肯定会导致团队中懂Rust的人和不懂Rust的人之间产生分裂。因为我们为这项服务选择了一种 "深奥 "的编程语言,公司里的其他工程师本来可以在构建功能、调试生产问题等方面提供帮助,但由于他们对Rust代码库摸不着头脑,所以基本上无法提供帮助。当你试图快速发展并利用团队中每个人的综合实力时,工程团队中缺乏可替代性可能是一个真正的责任。根据我的经验,人们在C++和Python等语言之间的转换一般没有什么困难,但Rust足够新,也足够复杂,这给人们的合作带来了障碍。

库和文档都不成熟
这个问题(我希望!)会随着时间的推移而得到解决,但与Go相比,Rust的库和文档生态系统是非常不成熟的。现在,Go的好处是在向世界发布之前,它是由Google的整个专业团队开发和支持的,所以文档和库都相当成熟。相比之下,Rust长期以来一直被认为是一个正在进行的工作。很多流行的库的文档是相当稀少的,人们经常需要阅读某个库的源代码来了解如何使用它。这很糟糕。

团队中的Rust辩护士经常会说 "async/await还很新 "和 "是的,那个库的文档很缺乏 "之类的话,但这些缺点对团队的影响相当大。我们在早期犯了一个巨大的错误,那就是采用Actix作为我们服务的网络框架,这个决定导致了巨大的痛苦和折磨,因为我们遇到了埋藏在库中的错误和问题,没有人能够想出如何解决。(公平地说,这是几年前的事了,也许现在情况已经有所改善了)。

当然,这种不成熟并不是Rust所特有的,但它确实是你的团队必须支付的一种税。不管核心语言的文档和教程有多好,如果你不知道如何使用这些库,那就没有什么意义了(当然,除非你打算从头开始写所有东西)。


Rust使得新功能的粗略设计变得非常困难
我不知道其他人怎么样,但至少对我来说,当我构建一个新的功能时,我通常不会事先考虑所有的数据类型、API和其他的细节。我经常只是在放屁,试图让一些基本的想法发挥作用,并检查我对事情应该如何工作的假设是否大致正确。在Python中这样做是非常容易的,因为你可以快速和宽松地处理诸如类型的事情,并且在你粗略地考虑你的想法时,不用担心某些代码路径被破坏。你可以稍后回去,让它变得整洁,修正所有的类型错误,并编写所有的测试。

在Rust中,这种 "编码草案 "是非常困难的,因为编译器可以而且会抱怨每一个没有通过类型和寿命检查的该死的东西--因为它是明确设计的。当你需要建立最终的、可用于生产的实现时,这是很有意义的,但当你试图把一些东西拼凑起来以测试一个想法或获得一个基本的基础时,这绝对是糟糕的。未实现的!宏在一定程度上是有帮助的,但仍然需要在编译前对堆栈上下的所有东西进行类型检查。

真正让人头疼的是,当你需要改变一个承重接口的类型签名时,你会发现自己花了好几个小时来改变每一个使用该类型的地方,只是为了看看你最初的尝试是否可行。当你意识到你需要再次改变它的时候,又要重新做所有的工作。

Rust的优点是什么?
我喜欢Rust的一些东西,也喜欢Rust的一些特性,我希望在其他语言中也能有这些特性。匹配语法很好。选项、结果和错误属性非常强大,而"?"操作符是一种处理错误的优雅方式。许多这些想法在其他语言中都有对应的功能,但Rust对它们的处理方式特别优雅。

对于那些需要高水平的性能和安全的项目,我绝对会使用Rust,对于这些项目,我并不十分担心需要和整个快速成长的团队一起快速进化代码的主要部分。对于单个项目,或者非常小的(比如2-3人)团队,Rust很可能就很好。对于像内核模块、固件、游戏引擎等对性能和安全性要求很高的项目来说,Rust是一个很好的选择,在这种情况下,可能很难在交付前做真正彻底的测试。


Reddit网友
1、像 Spring Boot 这样的行业标准和经过实战检验的框架将让您在几分钟内启动并运行……更不用说“Java 生态系统”中近 30 年的工具、库和社区了。太多的“工程师”很难处理他们的工作不是玩有趣的技术,而是交付产品

2、我只是用一个 200 行的 Rust 服务替换了一个 1200 行的 Spring Boot 服务,它做的更多、更快,并且其部署容器的大小不到 Java 容器大小的 1/30。它解决了原本需要在经过验证的臃肿 Java 实现中实现的问题。

3、问题不在于把Rust用在它本不应该用的地方,而在于把Rust用在生态系统还不成熟的地方。当你不关心低级别的细节时,Rust语言实际上做了一个了不起的工作,让你感觉自己是一个高级语言。一旦你适应了Rust,它的编写就会变得简单而快速,而且比我知道的任何其他语言都更容易让人相信你的代码会工作。
也就是说,在Rust的生态系统中,没有任何东西可以让你像NextJS那样,把一个功能齐全、可以投入生产的网络应用放在一起。NextJS有一个非常丰富的生态系统,你可以通过安装和配置正确的软件包在一天内解决大多数常见问题。要想让Rust取代这一点,还需要很长一段时间。

4、他们选错了工具。作为首席技术官,我对基础技术的决策是:

  • 它有满足我需要的可靠、稳定的库吗?
  • 它有一个庞大的社区(包括在 SO 上)吗?
  • 我和我的几个开发人员有经验吗?
  • 我可以租吗?在我所在的地区或时区,经验丰富的开发人员是否稀有且价格昂贵?如果我的专家离开了而我需要更换他们,我有多糟?人们对使用它感到兴奋吗?
  • 它是否满足我的产品的性能和速度要求?

理想情况下,我可以对所有这些回答是。与替代方案相比,Rust 不适用于网络Web应用程序。

5、我不认为 Rust 对于 CRUD API 来说很棒,但是没有人真的在那里说“绝对不要将 Rust 用于 CRUD API”。事实上,有用于 Rust web 开发的箱子、书籍和网站(有警告):https ://www.arewewebyet.org/

6、我个人喜欢 Rust webapp 框架,比如 Rocket,并且喜欢编译器提醒你并迫使你处理常见的陷阱,比如可为 null 的数据和验证。
本文中提到的所有内容都是完全有效的批评,可能减去文档部分(Rust 文档非常好)。我没有在初创公司工作过,所以我认为对“速度”的需求使 Rust 在该领域成为一个弱者。

7、在使用 Go 很多次之后,我认为在这方面它并不比 Typescript 好多少,我称之为真正的中间立场。
Go 将代码表现力发挥到极致,这经常导致大量代码重复——例如查看错误处理的样子。此外,无处不在的 goroutine 将并发性带到了极致,这使得它在某些网络繁重的场景中实际上比 python 慢,在这些场景中每个网络帧都开始通过 goroutines 调度程序。

8、抱歉,我认为具有足够经验的 Rust 可以成为一种非常高效的语言。
我必须承认,我花了很长时间才达到目前的 Rust 熟练程度,但现在我在 Rust 方面的工作效率远高于我在工作中使用的语言(例如 Go)。
话虽这么说,除非其他人都对 Rust 感到非常满意,否则我不会在工作中用 Rust 实现新服务,这主要是因为“Rust 有一个巨大的学习曲线”。
问题不在于 Rust 的生产力不好,问题在于它需要花费大量的时间和精力才能达到比其他语言更高的生产力。

 

1