21CTO导读:本文的作者思考了他在从Go辅以向学习Rust时遇到的有价值的事。
我写Go 代码已经超过了 5 年。我并没有主动去寻找Rust——它是被卷入我的世界的。
老实说,一开始我对它还真有些抵触。
当我回到Transcelestial 后,有一位同事正在开发一个工具,用于使用 A/B 分区设置(A 分区为活动分区,B 分区为备用分区;启动失败时会回退到仍然可用的分区)来刷写 Raspberry Pi 的系统。
他用 Rust 编写了这个工具。而我的第一反应是怀疑,甚至有点抵触:为什么要引入另一种语言?!
我们团队一直用的是 Go,这个工具很小巧,自成一体,而且有各种优点——好吧,事情已经发生了。
我认为这是一个不错的、低风险的方式来了解 Rust。
然后,又出现了一个命令行界面,这次是对我们某个后端的一系列查询的封装。我记得当时盯着代码,心想这都什么玩意儿:fn而不是逗号func、impl逗号、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 的概念可以沿用(例如 mpsc 到 struct,struct 到 struct,interface 到 traits),但很多都不能。Rust 的学习曲线确实比较陡峭——如果你之前用过 C/C++,学习起来可能会更顺畅一些。我的入门方式和很多人一样:阅读Rust 官方书籍及其引用的文档,在Playground里摸索尝试。感觉还不错。
但是,阅读 Rust 书籍和实际用它开发完全是两码事。
打击我的最大几件事情,大致按顺序排列:
没有return,你……不需要它?!(最后一个表达式是值)#[derive]?!async和async move需要生命周期的 `future`,再加上在异步作用域中移动和复制值和引用。这一下子就把难度提升到了一个新的高度。回想起来,最大的教训是:尽早理解记忆模型、借用检查器和生命周期理论。不要浅尝辄止。这会让你受益匪浅。
值得吗?绝对值得。我一点也不后悔。
说来也怪,我一开始最讨厌的一点——编译器和语言的严格性——现在恰恰是我最喜欢的一点。
Option我喜欢这个功能。真希望 Go 语言也有它。它明确地展现了可选性——你根本无法在不处理它可能为真值的情况下使用这个值None。Result对我来说?完全是全新的概念,但一旦理解了,就完全说得通了。不再是:val,err:=doThing()if err!=nil{return err}
反而:
let val = do_thing()?;光是这一点就让我更加喜欢这门语言了。
match真心不错。mpsc标准库中已经有了通道mpmc(正在开发中)。并非一切都尽如人意:
return奇特之处在于——你可以使用它,但你并非必须使用它——当两种风格混合使用时,可能会令人困惑。Box/Pin仍然是我不常使用或完全理解的东西。那我怀念 Go 的什么呢?说实话,不算多。Rust 对我来说很好用,但是我也很乐意用 Go 写代码。
根据我的需求,我会选择使用哪一个。
我不能透露太多关于目前技术栈的信息,但大致来说:我们编写的大部分代码都运行在Kubernetes上——大量的(微)服务和一些命令行工具。服务大多使用 gRPC 或 HTTP,并由常见的异步生态系统承担繁重的计算任务。
我们运行着一个单体仓库/工作区,效果很好。
我在思考一个问题:如今很多代码都是由大语言模型(LLM)生成的,尤其是对于Rust新手会这样去做。
说实话,我对这种现象的感受还很复杂——它降低了入门门槛,但基础知识仍然至关重要,我认为不可省略。
对于一家在现有技术栈上投入巨资的公司来说,引入 Rust 并非一件易事。其中一些阻力完全可以理解。
但是,那些编写 Rust 代码并将服务部署到生产环境的团队成员会对结果很满意:这些服务非常稳定,一旦上线运行,几乎无需维护。
现在有了LLM(生命周期管理)工具,入职流程更容易了。招聘很困难——但招聘任何技术领域的人才都很困难(反正我招聘时也不会针对特定技术领域;人们可以提升技能)。
我最怀念的是人工代码审查。LLM审查越来越受欢迎,而且某种程度上也有效,但我怀念人性的一面,比如关于什么是惯用写法、什么是不惯用的辩论,个人偏好,以及仔细的审视。正是这些讨论才能产生最好的代码,因为代码是在压力下运行的。而LLM审查有时会显得不屑一顾。
百分之百,是的。但这并不适合所有人:
如果你决定投身编程领域:那就从基础开始。把书和文档全部读完。真正理解借用检查器和生命周期(这是必须的),以及异步执行器和 Future 的实际工作原理。
然后,开始编写代码,请用你自己的方式,而不是交给 LLM,并从经验(或称痛苦的经历)中学习。
在我使用 Rust 开发的所有项目中,只遇到过两次程序崩溃。
其中一次是异步重试处理方式的问题:递归调用导致堆栈不断增长直至达到最大值,最终崩溃——后来改用迭代方式解决了这个问题。另一次是程序试图读取一个不存在的文件。
两个,就这两个。好吧,或许还有其他的,但都无关紧要,可以忽略不计。
相比之下,我使用 C++ 的经历就好得多了。公平地说,我并不是经验丰富的 C++ 工程师,所以犯了很多新手错误,比如遇到很多的运行时内存问题。
这场旅程并不轻松。但是在编译器的严格性和运行时保证变好了,这一切的努力都是值得的。
作者:聆听音乐的鱼
本篇文章为 @ 万能的大雄 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 微信公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。
请扫描二维码,使用微信支付哦。