17611538698
webmaster@21cto.com

我是如何从 Go 到 Rust的

图片

21CTO导读:本文的作者思考了他在从Go辅以向学习Rust时遇到的有价值的事。

我写Go 代码已经超过了 5 年。我并没有主动去寻找Rust——它是被卷入我的世界的。

老实说,一开始我对它还真有些抵触。

我是如何被卷入其中的


当我回到Transcelestial 后,有一位同事正在开发一个工具,用于使用 A/B 分区设置(A 分区为活动分区,B 分区为备用分区;启动失败时会回退到仍然可用的分区)来刷写 Raspberry Pi 的系统。

他用 Rust 编写了这个工具。而我的第一反应是怀疑,甚至有点抵触:为什么要引入另一种语言?!

我们团队一直用的是 Go,这个工具很小巧,自成一体,而且有各种优点——好吧,事情已经发生了。

我认为这是一个不错的、低风险的方式来了解 Rust。

然后,又出现了一个命令行界面,这次是对我们某个后端的一系列查询的封装。我记得当时盯着代码,心想这都什么玩意儿fn而不是逗号funcimpl逗号、Option逗号、Result逗号。#[derive(...)]这太让人困惑了,实在难以理解。

在接下来的几天里,我阅读了更多关于 Rust 的资料,就越觉得它的独特,也越觉得它很有趣。

让这一切成为现实的项目


然后,一个机会摆在了我的面前:从头开始重建我们用来操作设备的 CLI 之一,以及在设备上运行的一组服务,用于流式传输设备的状态(健康状况、软件/固件信息、传感器数据等等)。

有两个原因促进我选择使用 Rust 来开发这个项目。

第一个原因,消息传递。

在服务之间以及服务与命令行界面 (CLI) 之间存在大量的 mpsc 和 mpmc 式通信。在 Go 中,mpsc 非常简单——只需一个简单的消息传递即可chan。但 mpmc就比较麻烦了,因为每个消费者都需要接收生产者发送的每条消息。Rust 有一个 crate 正好可以满足我的需求。

第二个原因,内存。设备上的服务需要占用少量且有限的内存,而且我希望对内存有一定的控制权。在 Go 语言中,垃圾回收器 (GC) 会帮你管理内存,你几乎无法控制如何以及何时回收。而在 Rust 中,内存管理则完全由你自己负责,就像 C 或 C++ 一样——没有人会替你清理内存。(当然,Rust 提供了一系列编译时保证,所以你不会犯太低级的错误,但你仍然有可能在运行时犯错。)

所以我尝试了一下,用 Rust 重新构建了 CLI 和服务。

我首先要解决的问题之一就是如何从.proto文件中生成代码,就像我们在 Go 语言中那样(生成的代码提交到代码库)。

prost了我的大忙,基于生成的代码实现服务逻辑的过程出乎意料地顺利。CLI 也一样——clap完全可以胜任。唯一让我感到意外的是异步操作:当时我不太明白它的运行原理,但tokio让它变得(基本)易于管理。

当时我一边写着一堆 Rust 代码,一边还要维护和编写 Go 代码。心情真的是五味杂陈。😅

来自 Go 的养料


但我不会粉饰太平:过渡期并不容易。

有些 Go 的概念可以沿用(例如 mpsc 到 struct,struct 到 struct,interface 到 traits),但很多都不能。Rust 的学习曲线确实比较陡峭——如果你之前用过 C/C++,学习起来可能会更顺畅一些。我的入门方式和很多人一样:阅读Rust 官方书籍及其引用的文档,在Playground里摸索尝试。感觉还不错。

但是,阅读 Rust 书籍和实际用它开发完全是两码事。

打击我的最大几件事情,大致按顺序排列:

  1. 编译器非常严格。它抛出了好多错误信息。光是阅读和理解这些错误信息就需要一段时间适应。
  2. 没有return你……不需要它?!(最后一个表达式是值)
  3.  是什么叫#[derive]?!
  4. 借用检查器。这个功能过去是——现在有时仍然是——一个难题。它不会因为写更多代码就神奇地变得清晰明了(我查阅了大量 Stack Overflow 和论坛;那时候人工智能还没出现)。
  5. 异步。我刚以为自己掌握了所有权和生命周期,就又出现了 `undefined`、`undefined`asyncasync move需要生命周期的 `future`,再加上在异步作用域中移动和复制值和引用。这一下子就把难度提升到了一个新的高度。


回想起来,最大的教训是:尽早理解记忆模型、借用检查器和生命周期理论。不要浅尝辄止。这会让你受益匪浅。

值得吗?绝对值得。我一点也不后悔。

我现在喜欢的东西


说来也怪,我一开始最讨厌的一点——编译器和语言的严格性——现在恰恰是我最喜欢的一点。

  • 再也不会出现空指针 panic 了。我在 Go 中修复过无数个这样的 panic:无论我们写了多少单元测试和集成测试,总会有一些依赖项被当作指针传递,而我们却忘记进行配置。在 Rust 中,这已经不再算作 bug 了。
  • Option我喜欢这个功能。真希望 Go 语言也有它。它明确地展现了可选性——你根本无法在不处理它可能为真值的情况下使用这个值None
  • Result对我来说?完全是全新的概念,但一旦理解了,就完全说得通了。不再是

val,err:=doThing()if err!=nil{   return err}

反而:

let val = do_thing()?;

光是这一点就让我更加喜欢这门语言了。

  • match真心不错
  • mpsc标准库中已经有了通道mpmc(正在开发中)。
  • cargo + crates + std。Go拥有庞大的生态系统,但我发现更容易在 Rust 的 std 或成熟的 crate 中找到我需要的东西。
  • 源文件中的测试。我觉得这很有意义。
  • 泛型。我还在犹豫——它们比 Go 的泛型更健壮,但一旦与异步操作混合使用,就会变得很棘手。


是什么还在与我作对


并非一切都尽如人意:

  • 这种return奇特之处在于——你可以使用它,但你并非必须使用它——当两种风格混合使用时,可能会令人困惑。
  • 异步操作、借用检查器和生命周期管理有时仍然难以兼顾。
  • Box/Pin仍然是我不常使用或完全理解的东西。
  • 泛型与异步混合使用。
  • 对于简单的程序来说,Go 语言的编写流程非常简单。一个小型 HTTP 服务器和几个处理程序几乎不费吹灰之力。而 Rust 则需要多做一些准备工作(当然,这或许是多年 Go 语言使用习惯造成的肌肉记忆作祟)。
  • 如果依赖项很多,构建时间可能会很长——公平地说,Go 的构建时间也会很长。


那我怀念 Go 的什么呢?说实话,不算多。Rust 对我来说很好用,但是我也很乐意用 Go 写代码。

根据我的需求,我会选择使用哪一个。

我今天如何使用它


我不能透露太多关于目前技术栈的信息,但大致来说:我们编写的大部分代码都运行在Kubernetes上——大量的(微)服务和一些命令行工具。服务大多使用 gRPC 或 HTTP,并由常见的异步生态系统承担繁重的计算任务。

我们运行着一个单体仓库/工作区,效果很好。

我在思考一个问题:如今很多代码都是由大语言模型(LLM)生成的,尤其是对于Rust新手会这样去做。

说实话,我对这种现象的感受还很复杂——它降低了入门门槛,但基础知识仍然至关重要,我认为不可省略。

从团队角度


对于一家在现有技术栈上投入巨资的公司来说,引入 Rust 并非一件易事。其中一些阻力完全可以理解。

但是,那些编写 Rust 代码并将服务部署到生产环境的团队成员会对结果很满意:这些服务非常稳定,一旦上线运行,几乎无需维护。

现在有了LLM(生命周期管理)工具,入职流程更容易了。招聘很困难——但招聘任何技术领域的人才都很困难(反正我招聘时也不会针对特定技术领域;人们可以提升技能)。

我最怀念的是人工代码审查。LLM审查越来越受欢迎,而且某种程度上也有效,但我怀念人性的一面,比如关于什么是惯用写法、什么是不惯用的辩论,个人偏好,以及仔细的审视。正是这些讨论才能产生最好的代码,因为代码是在压力下运行的。而LLM审查有时会显得不屑一顾。

我会再做一次吗?


百分之百,是的。但这并不适合所有人:

  • 如果 Go 能完成这项工作,那就用 Go。 :)
  • 当你面临真正的限制时,请选择Rust:内存控制、生产稳定性或接近底层硬件的需求。
  • 如果你的技术栈已经是 Rust,那就继续吧。
  • 如果你的技术栈是 Go,那么除非有充分的理由(例如 Go 实际能提供的性能之外的性能),否则不要切换。

如果你决定投身编程领域:那就从基础开始。把书和文档全部读完。真正理解借用检查器和生命周期(这是必须的),以及异步执行器和 Future 的实际工作原理。

然后,开始编写代码,请用你自己的方式,而不是交给 LLM,并从经验(或称痛苦的经历)中学习。

最能说明问题的数据


在我使用 Rust 开发的所有项目中,只遇到过两次程序崩溃。

其中一次是异步重试处理方式的问题:递归调用导致堆栈不断增长直至达到最大值,最终崩溃——后来改用迭代方式解决了这个问题。另一次是程序试图读取一个不存在的文件。

两个,就这两个。好吧,或许还有其他的,但都无关紧要,可以忽略不计。

相比之下,我使用 C++ 的经历就好得多了。公平地说,我并不是经验丰富的 C++ 工程师,所以犯了很多新手错误,比如遇到很多的运行时内存问题。

这场旅程并不轻松。但是在编译器的严格性和运行时保证变好了,这一切的努力都是值得的。

作者:聆听音乐的鱼

评论

我要赞赏作者

请扫描二维码,使用微信支付哦。

分享到微信