引言:在工作里应用 Rust 曾经有两个多月的工夫了,谈谈我做为一名多年的 C 系(C、C++)程序员,对 Rust 的初体验。
一个 C 系程序员的 Rust 初体验
最近因为工作的起因,应用上了 Rust 语言,在此之前我有多年的 C、C++ 编码教训(以下将 C、C++ 简称 C 系语言)。
应用 C 系语言编码时,最常常面对的问题就是内存问题,诸如:
- 野指针(Wild Pointe):应用了不可知的指针变量,如曾经被开释、未初始化、随机,等等。
- 内存地址因为拜访越界等起因被笼罩(overflow),这不然而可能出错的问题,还有可能成为程序的内存破绽被利用。
- 内存调配后未回收。
连 Chrome 的报告都指出,Chrome 中大概 70% 的安全漏洞都是内存问题。见:Memory safety[1]。
C 系语言倒退到明天,曾经有不少能够用于内存问题检测的利器了,其中最好用的莫过于 AddressSanitizer[2],它的原理是在编译时给程序加上一些信息,一旦产生内存越界拜访、野指针等谬误都会自动检测进去。
然而即使有这些工具,内存问题也不好解决,其外围的起因在于:这些问题绝大部分都是运行时(Runtime)问题,即要在程序跑到特定场景的时候才会裸露进去,诸如下面提到的 AddressSanitizer 就是这样。
都晓得解决问题的第一步是能复现问题,而如果一个问题是运行时问题,这就意味着:复现问题可能会是一件很麻烦的事件,有时候还可能到生产环境去复现。
以我之前经验的一个 Bug 来看这类工作的复杂度,见 线上存储服务解体问题剖析记录 – codedump 的网络日志 [3],这是一个很典型的产生在生产环境上因为内存谬误导致的解体问题:
- 不好复现,因为跟特定的申请相干,还跟线程的调度无关;
- 实质是因为应用了被开释的内存导致的谬误。
这个线上问题,记得过后花了一周工夫来复现问题解决。
换言之,如果一个问题要等到运行时能力发现,那么能够预感的是:一旦呈现问题,要复现问题可能要花费大量的精力,以及须要很多教训才行。如果一个问题还是在特定场景,或者用户现场才呈现的,那就更麻烦了,C 系程序员以往个别都是这样来保留“现场”:
- 呈现解体的时候保留 core 文件来查看调用堆栈、变量等信息。
- 创造了各种复制流量重放的工具,比方 tcpcopy[4] 等。
总而言之,运行时问题一旦呈现是很麻烦的,而解决这类问题的工夫是难以预期的。
Rust 给这类内存问题的解决提供了另一个解决思路:
- 一个内存地址同时只能被一个变量应用。
- 不能应用未初始化的变量。
- …
简而言之,但凡可能呈现内存谬误的中央,都在语言的语法层面给予禁止,换来的就是更多的编译工夫,因为要做这么多查看嘛,而须要更多的编译工夫反过来就须要更好的硬件。我想这也是 Rust 到了最近几年才开始缓缓风行开来的起因之一,毕竟即使是当初,一些大型的 Rust 我的项目一般的机器编译起来也还是很耗时。
“编译工夫(compile time)”是一个能够预期的固定工夫,能通过减少硬件性能(比方买更好的机器来写 Rust)来解决;而“运行时问题”一旦呈现,查找起来的工夫、精力、场景(比方呈现在用户现场、几百万次能力重现一次等)不确定性可就很高了。
两者衡量,我抉择解决“编译工夫”问题。而且,在我意识到有这样的工具可能在编译期解决大部分内存问题时,反过来再看应用 C 系语言的我的项目,简直能够预期的是:只有代码和复杂度上了肯定规模,那么这类我的项目都要花上相当的一段时间能力稳定下来。起因在于:相似内存问题这样的运行时问题,是须要场景去积攒,能力裸露进去的,而场景的积攒,就须要很多的小白鼠和运行工夫了。
总结一下我的观点:
- C 系语言最多的问题就是各类内存问题,而这些问题大多是运行时问题。即使当初曾经有了各种工具,解决其运行时问题也很艰难。
- Rust 解决这类问题的思路,是在语法层面禁止所有可能呈现内存问题的操作,换来的代价就是更多的编译工夫。
- 解决可预期的“编译工夫”和难预期的“运行时问题”,我抉择前者。
番外篇
rr
rr: lightweight recording&deterministic debugging[5] 也是出自 Mozilla 的另一款调试 C 系程序的利器,rr 是 Record and Replay 的简称,目标还是为了解决各种运行时问题,因为运行时问题中存在着各种不确定的因素,包含:
- 变量值。
- 过程、线程环境,比方不同的线程调度程序可能导致了不同的后果。
- 输出不同的数据,能失去不同的后果。
于是,rr 要解决的外围问题,就是让一个程序在运行时有一个固定的环境,它能够抓取程序运行的环境保留下来。这样在呈现问题之后,就能应用它能够记录下来程序运行时的环境,不停的重放来调试解决问题。
然而,即使是这样,rr 可能更适宜于明确晓得问题的状况上来抓取环境,不可能在线上间接关上这个工具。所以又回到后面的论断了:调试运行时问题可能面对的艰难,包含场景、工夫、用户现场等等不确定因素。
rr 和 Rust 一样,都出自 Mozilla,我想不是偶尔的。Mozilla 和 chrome 等一样,都是应用 C++ 编码的超大型我的项目,而这里肯定遇到了各种运行时问题,不止于内存问题,所以才要应用各种工具来辅助解决这类问题。
吃上硬件降级的红利了吗?
后面提到过,Rust 目前较大的问题是编译工夫过长,这可能是导致它最近几年才开始逐步风行开来的起因。其实反过来说,在硬件降级之后,应该能尽量利用上硬件,在编译期尽量多查看出谬误来,缩小运行时发现问题的数量。这样,能力吃上硬件降级的红利,利用硬件来缩小本人的犯错。
一方面硬件降级给了编程语言能施展更大、更快的的“舞台”,随着舞台的更新,就会有更新、更好的工具呈现;另一方面做为从业者,也应该与时俱进,多学习跟进这些工具的演进。
我看到有一些人,强调本人多早就曾经用 C 语言写代码了,然而查内存问题还在用慢的不行的 Valgrind,没听过更不晓得怎么用 Address Sanitizer。想说如果技能点都曾经不更新了,强调多早学的有什么意义?好比 1950 年就会打算盘,有意义吗?强调多早就用 C 语言相似的舆论,在我看来就是“老气横秋”,然而技术突飞猛进的畛域,卖老的意义不大。
《Rust for Rustaceans》
举荐 Rust for Rustaceans[6] 作者 Jon Gjengset 的油管频道:https://www.youtube.com/c/Jon…
有很多很有深度的 Rust 分享,比方:
- Implementing TCP in Rust (part 1) – YouTube[7]
- Porting Java’s ConcurrentHashMap to Rust (part 1) – YouTube[8]
参考资料
[1]Memory safety: https://www.chromium.org/Home…
[2]AddressSanitizer: https://en.wikipedia.org/wiki…
[3] 线上存储服务解体问题剖析记录 – codedump 的网络日志: https://www.codedump.info/pos…
[4]tcpcopy: https://github.com/session-re…
[5]rr: lightweight recording & deterministic debugging: https://rr-project.org/
[6]Rust for Rustaceans: https://rust-for-rustaceans.com/
[7]Implementing TCP in Rust (part 1) – YouTube: https://www.youtube.com/watch…\_VAm8SXwt1X
[8]Porting Java’s ConcurrentHashMap to Rust (part 1) – YouTube: https://www.youtube.com/watch…\_Za1187rNfnl
对于咱们
Databend 是一款开源、弹性、低成本,基于对象存储也能够做实时剖析的旧式数仓。期待您的关注,一起摸索云原生数仓解决方案,打造新一代开源 Data Cloud。
- Databend 文档:https://databend.rs/
- Twitter:https://twitter.com/Datafuse\_Labs
- Slack:https://datafusecloud.slack.com/
- Wechat:Databend
- GitHub:https://github.com/datafusela…
文章始发于公众号:Databend