学习 rust 语言

2025-06-01

因为工作的一些内容调整,最近一阵都在学习 rust,已经有一个多月了,是时候写一点点 rust 的学习总结了。

学习之路

陆陆续续看了《Rust语言圣经》,然后是《Rust By Example》,以及做完了 《rustlings》的练习。感觉难度上是 《Rust语言圣经》 远大于《Rust By Example》,正确的路径其实应该先从《Rust By Example》开始看,然后做 rustings 练习,最后才看《Rust 语言圣经》。

其它的就是也看一看 youtube 上的视频或者演讲,不过都不成体系。关注下 rust 相关的博客,目前从这块都还没学习到太多内容。工作上还有日常本身的一些事情要处理,所以 rust 学习之路只是断断续续地补 rust 的基础知识。

面向任务的选择

许多年以前,我第一次学习 rust 的时候,其实挺难受的。名副其实的从入门到放弃。相比之前,这门语言也进化和成熟了许多,然后世界也变化了许多,现在有 AI,学习门槛也比之前低了一些,当然,这并不能改变掌握 rust 语言其实是挺难的这个事实。

那为什么要学它呢?直接的答案就是,工作需要嘛。背后我想聊一下,面向任务的选择。在特定领域,特定任务使用特定的工具,会更容易完成。这也是世界上有这么多的编程语言,基本上每一种流行的语言,都会有它自己的生态位。一定是有某些任务用它更容易完成的,因此它占据了这样的生态位,否则这门语言可能就凉凉了。

抛开工作的任务,看一看 rust 的生态位,我正好有一个例子。我挺喜欢的一个游戏叫天之炼狱,之前我修复了一份源代码,这是一个很大的 C++ 的工程。开源之后,后续并没有花太多时间在维护,最近的一个痛点就是可移植性太差了。这份代码是 vc6 环境,现在已经很难处理编译环境了。操作系统会变,window 不是一个好的开发平台,mac 和 linux 更友好一些;硬件环境会变,从前的 x86,到现在 m3 m4 可能将来更活跃的 arm 平台。现在一个很现实的问题就是,一旦失去编译环境,这份代码就永远是 dead code 了。一种方式是重新移植,也是需要大量的人力,老旧的 c++ 代码除了 vc6 换到现在的 g++ 都编译不过。

所以我想到的一个问题是,如果将它整个重写,会选择什么样的语言?当然,这个坑是不能轻易开的,开坑了肯定填不上,只会烂尾。所以现在也只能想想这个问题。从我的角度把候选的语言在这里列一列,然后一一点评下:

  • go
  • rust
  • zig
  • odin
  • c3
  • jai
  • austral
  • c
  • c艹
  • c + lisp

Go 是我自己最熟悉和善长的语言,但是对于这样的一个游戏项目,我不会选择 Go。 Go 最适合于后端开发,高并发,网络编程这块的开发体验尤其出色。但如果说它门一门"系统级"的编程语言,就需要重新定义啥是系统了,反正操作系统是系统,分布式系统也是系统嘛。游戏开发的实时性其实比后端要求更高一些,以 60 帧流畅角度来看,实际留给每帧渲染的处理时间也就 16ms,这里要处理游戏逻辑,更新组件状态,并且对单线程的计算能力要求更高,所以更适合传统的 C++ 以及新兴的 modern C 类的语言。基于垃圾回收的在这种场景下基本都要出局。

对于后端开发,面向终端用户大几百 ms 的延迟是可以接受的,抖动的影响明显没有游戏卡顿的体验糟糕。我以前做竞价服务的时候,那边的响应时间要求是 120ms,这里面会包含用户请求来回的 round trip,然后包含后端访问数据库的时间,所以留给业务本身的实际处理时间并不多,可能就 30-50ms 这种量级了。现在做数据库领域,也还是属于后端,对响应时间的要求更严格,响应延迟会被压缩到 1-10ms 这种量级,Go 也还是能玩一玩上层计算层的处理。总体上看,Go 语言跟 Java 同一生态位,主要抢的是部分的 python/php 这一类作为后端的市场份额。

在聊这些 modern C 的语言之前,先看看传统选择:C 和 c艹。

我不会用 C 来重写这样的大型游戏,是因为 C 语言提供的抽象能力不够。库和各种封装都不够顺手。属于理论上能写,但是写起来不舒适。至于 C艹,其实如果从维护老游戏维护老项目的角度,最好的做法就是继续沿用 C艹,不要瞎折腾。现在是 "如果重写" 嘛,那就排除 C艹了。这门语言复杂,恶心,学习成本高,不停地变化(十年前的代码你看它还能编译不),没有人能掌握这门语言。只要有更好的选择的时候,都不要碰它。

最理想的选择,我会选底层用 C,高层用一门动态语言结合,许多游戏和引擎都是这种模式的。游戏这块,很多人高层的语言会选 lua。对我自己而言,我会选 c + lisp,也就是我会用自己的 cora 语言重写。这是最理想的情况,但是实际上我不会这么干,主要是当前 cora 的成熟度还不够,调试能力,库,生态这些会让它很不称手。

然后看迫不得已的选择,modern C 类的语言。其实编程语言说白了就两类:有人骂的和没人用的。以前这个场景下玩家只有 C艹,所以只能捏着鼻子用。现在至少有更多的选择了。odin 和 jai 都是面向游戏领域而设计的编程语言,jai 还没有正式开源,odin 我看了一下,体感上像 Go,不过没有什么新鲜东西,就是一个更友好的 C。考虑到语言的体量太小,这门语言的使用经验会无法带到其它系统开发上,学习它不值得,还不如选 rust 和 zig。

austral 是一种比 rust 的 ownership + borrow check 系统限制还要严格的东西。也是强调安全的语言,只是实现方式和 rust 不一样。它是基于 linear type 的,语言设计偏形式化验证。感觉更多的应用领域可能是什么国防军工啥的,以前也有个语言叫 ada 的。linear type 看起来理解上比 ownership 要容易一些,也就是这门语言学习曲线可能没 rust 高,但是使用体验上应该是跟 rust 一样处处受限的。

对于所有的 modern C 语言来讲,如果没有什么 killer feature,只是一味地强调自己改进了 C 的某些点,这样的语言是注定无法流行的,原因无它,C 太优秀了。 小的改进收益太低,不足以说服人们迁移。像 D 语言之类的,都是失败的例子。像上面提到的 c3 我也不看好它。

最终的决胜局只剩下 rust vs zig。

rust vs zig 哲学对比

坦白说,从语言的设计哲学角度,我更喜欢 zig。rust vs zig 更像是 c vs c++。喜欢 c 的人更可能喜欢 zig,而喜欢 c++ 的人则更可能喜欢 rust。

rust 语言的亮点是内存安全,zig 语言的亮点是 comptime。zig 编译系统也是亮点,不过算不上 killer 级别的 feature。

zig 更倾向于把所有东西暴露出来,都是显示的而不是隐式的。rust 语言中就是有太多隐式的东西,好处是这些东西知道了之后可以少写很多代码,缺点是知道的过程付出的学习成本很不平滑。用 zig 的话术叫:"让用户去 debug 代码而不是 debug 语言"。当一个语言需要太多隐式的抽象的时候,就会出现 debug 语言的现象。比如去 debug rust 的 async 代码。或者理解为什么有 Copy 或者 Clone 之类的,这都是背后做了许多复杂的抽象。

我之前说过,对于内存管理,编程语言有两种选择:一种是"内存管理这么重要的事情,怎么能交给程序员去处理呢"?,另一种是"内存管理这么重要的事情,怎么能丢给语言或者runtime呢"? zig 选择相信程序员,所以并不是完全的内存安全的。而 rust 则是不信任程序员。

rust 和 zig 都提供了错误处理,至少是比 Go 语言做的好得多。其实把错误处理做好,然后在语言中消除掉 null,就已经可以消灭掉很大一部分的内存安全问题了。zig 做到的是一个比较平衡的程度,不走极端,而 rust 这边做到完全安全,付出的代价很高,它改变了程序员的使用习惯,限制他们的表达方式才达到目的。

zig 是 “small is beautiful” + “C without footguns”

不管是投资 zig 还是投资 rust,最终都会有所收益。如果是个人选择,其实我是会选择 zig。但是在精力有限,工作需要的情况下,选 rust 显得更明智。

毕竟如果要把那个几十万行的C艹游戏代码重写是个只能想一想的事情,伪需求,而工作则是切切实实地要用到 rust 呢。我也可以选择工作用 rust,自己业余使用去尝试一下 zig,但是精力毕竟是有限的,我的终极语言其实还是 c+lisp,与其分散精力,还不如集中力量把 cora 好好推进。

面向协作的选择

单人项目跟协作项目会有很大的不一样。以我对 Go 语言的熟悉程度,哪怕真选择用它重写一个游戏项目,其实可行性也是没问题的。毕竟 Go 这边也有 ebitengine 这样的游戏引擎,也有 OpenDiablo2 这样的游戏项目存在。

涉及到协作就很不一样。看一些开源项目,如果是单人作品,往往是小而精,代码风格也很一致且优雅。而如果是一个大项目,被一堆人往里面堆屎,基本就是一个巨大的草台班子。在协作上面,Go 就不如 rust。当我一个人写一些东西,用 Go 写的时候,我是可以保证一些性能层面的东西的。然而一群人一起写一个东西的时候,就只有语言层面来最低限度地保证性能这个事情了,而且想优化都优化不动。因为只要无法阻止堆屎行为,就无法阻止代码的腐烂变质。破窗效应。

C 语言的协作就更危险,写 Go 的协作顶多是性能层面的问题,而写 C 的协作那真的叫 footgun。也难怪 linux 内核那边会要推 rust。rust 在协作上是有优势的,它的高学习门槛正好把堆屎的人拦到外面。不再是"与人斗,其乐无穷",不再是相互伤害,而是变成"让编译器天天敲你的头,教你怀疑人生"。

学习的心态

其实 rust 难学的部分原因是在于心态。"如果一种语言不能改变你对编程的理解,那么这门语言就不值得学习"。我学习过的许多语言中,都验证了这一点。 改变对编程的理解,都是要付出巨大的学习代价的。最极端的语言里面往往能学到最多的知识,而最中庸的语言可能使用最顺手,却学不到什么。

像 C 语言,C和指针是最精华的部分,也是将不适合学编程的人挡在门外的巨大障碍。学习 C 对于增加对系统底层的理解,编译,链接,系统加载,等等所有底层知识都是有巨大收益的。Go语言并发模型,在理解之后就再也不回不去基于锁和共享内存的并发了。从 scheme 语言中可以学习递归。haskell 的类型是其精华。哪怕是lua脚本语言,也可以从中学习到了带GC语言与C语言之间如何交互,我说过lua是极少数把这个事情做对的。函数式编程,没有赋值怎么玩,只有适应语言之后,才会解锁新的编程模式。

放到 rust 上面,也是同样的,心态很重要。学习一门新的语言就必须适应它的思维方式,然后才能理解这门语言。

像学习 C艹一样学习 rust

rust 和 C艹 有很多相似之处,想想我自己学习 C艹 语言的时候,就是死记硬背,学书本知识而不是背后的原理。

C艹 其实是一门实用的语言,可以骂这门语言操蛋,但不能否认它是从实际出发而设计的。这门语言很庞大,其原因是它想要一门语言能解决所有的问题,什么都往里面塞,所以才导致它的复杂和庞大。而现实世界中,待解决的问题本身是客观存在的。当初学完理论知识,还没有到能够应用到现实问题上,就弃坑 C艹 了。

而现在工作中对实际的问题理解更深刻,像学习 C艹 一样学习 rust 是可以对照理论和实践的。这不,还有写了一个对照的,挺值得一读的。

rust