一個 C 系程序員的 Rust 初體驗
引言:在工作里使用 Rust 已經有兩個多月的時間了,談談我做為一名多年的 C 系(C、C++)程序員,對 Rust 的初體驗。
一個 C 系程序員的 Rust 初體驗
最近由于工作的原因,使用上了 Rust 語言,在此之前我有多年的 C、C++ 編碼經驗(以下將C、C++ 簡稱 C 系語言)。
使用 C 系語言編碼時,最經常面對的問題就是內存問題,諸如:
野指針(Wild Pointe):使用了不可知的指針變量,如已經被釋放、未初始化、隨機,等等。
內存地址由于訪問越界等原因被覆蓋(overflow),這不但是可能出錯的問題,還有可能成為程序的內存漏洞被利用。
內存分配后未回收。
連 Chrome 的報告都指出,Chrome 中大約 70% 的安全漏洞都是內存問題。見:Memory safety[1]。
C 系語言發(fā)展到今天,已經有不少可以用于內存問題檢測的利器了,其中最好用的莫過于 AddressSanitizer[2],它的原理是在編譯時給程序加上一些信息,一旦發(fā)生內存越界訪問、野指針等錯誤都會自動檢測出來。
但是即便有這些工具,內存問題也不好解決,其核心的原因在于:這些問題絕大部分都是運行時(Runtime)問題,即要在程序跑到特定場景的時候才會暴露出來,諸如上面提到的 AddressSanitizer 就是這樣。
都知道解決問題的第一步是能復現(xiàn)問題,而如果一個問題是運行時問題,這就意味著:復現(xiàn)問題可能會是一件很麻煩的事情,有時候還可能到生產環(huán)境去復現(xiàn)。
以我之前經歷的一個 Bug 來看這類工作的復雜度,見?線上存儲服務崩潰問題分析記錄 - codedump 的網絡日志[3],這是一個很典型的發(fā)生在生產環(huán)境上由于內存錯誤導致的崩潰問題:
不好復現(xiàn),因為跟特定的請求相關,還跟線程的調度有關;
本質是由于使用了被釋放的內存導致的錯誤。
這個線上問題,記得當時花了一周時間來復現(xiàn)問題解決。
換言之,如果一個問題要等到運行時才能發(fā)現(xiàn),那么可以預見的是:一旦出現(xiàn)問題,要復現(xiàn)問題可能要花費大量的精力,以及需要很多經驗才行。如果一個問題還是在特定場景,或者用戶現(xiàn)場才出現(xiàn)的,那就更麻煩了,C 系程序員以往一般都是這樣來保存“現(xiàn)場”:
出現(xiàn)崩潰的時候保存 core 文件來查看調用堆棧、變量等信息。
發(fā)明了各種復制流量重放的工具,比如 tcpcopy[4]?等。
總而言之,運行時問題一旦出現(xiàn)是很麻煩的,而解決這類問題的時間是難以預期的。
Rust 給這類內存問題的解決提供了另一個解決思路:
一個內存地址同時只能被一個變量使用。
不能使用未初始化的變量。
...
簡而言之,凡是可能出現(xiàn)內存錯誤的地方,都在語言的語法層面給予禁止,換來的就是更多的編譯時間,因為要做這么多檢查嘛,而需要更多的編譯時間反過來就需要更好的硬件。我想這也是 Rust 到了最近幾年才開始慢慢流行開來的原因之一,畢竟即便是現(xiàn)在,一些大型的 Rust 項目普通的機器編譯起來也還是很耗時。
“編譯時間(compile time)”是一個可以預期的固定時間,能通過增加硬件性能(比如買更好的機器來寫 Rust)來解決;而“運行時問題”一旦出現(xiàn),查找起來的時間、精力、場景(比如出現(xiàn)在用戶現(xiàn)場、幾百萬次才能重現(xiàn)一次等)不確定性可就很高了。
兩者權衡,我選擇解決“編譯時間”問題。而且,在我意識到有這樣的工具能夠在編譯期解決大部分內存問題時,反過來再看使用 C 系語言的項目,幾乎可以預期的是:只要代碼和復雜度上了一定規(guī)模,那么這類項目都要花上相當的一段時間才能穩(wěn)定下來。原因在于:類似內存問題這樣的運行時問題,是需要場景去積累,才能暴露出來的,而場景的積累,就需要很多的小白鼠和運行時間了。
總結一下我的觀點:
C 系語言最多的問題就是各類內存問題,而這些問題大多是運行時問題。即便現(xiàn)在已經有了各種工具,解決其運行時問題也很困難。
Rust 解決這類問題的思路,是在語法層面禁止一切可能出現(xiàn)內存問題的操作,換來的代價就是更多的編譯時間。
解決可預期的“編譯時間”和難預期的“運行時問題”,我選擇前者。
番外篇
rr
rr: lightweight recording&deterministic debugging[5]?也是出自 Mozilla 的另一款調試 C 系程序的利器,rr 是 Record and Replay 的簡稱,目的還是為了解決各種運行時問題,由于運行時問題中存在著各種不確定的因素,包括:
變量值。
進程、線程環(huán)境,比如不同的線程調度順序可能導致了不同的結果。
輸入不同的數據,能得到不同的結果。
于是,rr 要解決的核心問題,就是讓一個程序在運行時有一個固定的環(huán)境,它可以抓取程序運行的環(huán)境保存下來。這樣在出現(xiàn)問題之后,就能使用它可以記錄下來程序運行時的環(huán)境,不停的重放來調試解決問題。
但是,即便是這樣,rr 可能更適合于明確知道問題的情況下去抓取環(huán)境,不可能在線上直接打開這個工具。所以又回到前面的結論了:調試運行時問題可能面對的困難,包括場景、時間、用戶現(xiàn)場等等不確定因素。
rr 和 Rust 一樣,都出自 Mozilla,我想不是偶然的。Mozilla 和 chrome 等一樣,都是使用 C++ 編碼的超大型項目,而這里一定遇到了各種運行時問題,不止于內存問題,所以才要使用各種工具來輔助解決這類問題。
吃上硬件升級的紅利了嗎?
前面提到過,Rust 目前較大的問題是編譯時間過長,這可能是導致它最近幾年才開始逐漸流行開來的原因。其實反過來說,在硬件升級之后,應該能盡量利用上硬件,在編譯期盡量多檢查出錯誤來,減少運行時發(fā)現(xiàn)問題的數量。這樣,才能吃上硬件升級的紅利,利用硬件來減少自己的犯錯。
一方面硬件升級給了編程語言能施展更大、更快的的“舞臺”,隨著舞臺的更新,就會有更新、更好的工具出現(xiàn);另一方面做為從業(yè)者,也應該與時俱進,多學習跟進這些工具的演進。
我看到有一些人,強調自己多早就已經用 C 語言寫代碼了,但是查內存問題還在用慢的不行的 Valgrind,沒聽過更不知道怎么用 Address Sanitizer。想說如果技能點都已經不更新了,強調多早學的有什么意義?好比 1950 年就會打算盤,有意義嗎?強調多早就用 C 語言類似的言論,在我看來就是“倚老賣老”,但是技術日新月異的領域,賣老的意義不大。
《Rust for Rustaceans》
推薦 Rust for Rustaceans[6]?作者 Jon Gjengset 的油管頻道:https://www.youtube.com/c/JonGjengset/playlists
有很多很有深度的 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/chromium-security/memory-safety/
[2]AddressSanitizer:?https://en.wikipedia.org/wiki/AddressSanitizer
[3]線上存儲服務崩潰問題分析記錄 - codedump的網絡日志:?https://www.codedump.info/post/20190413-problem-fix/
[4]tcpcopy:?https://github.com/session-replay-tools/tcpcopy
[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?v=bzja9fQWzdA&list=PLqbS7AVVErFivDY3iKAQk3\_VAm8SXwt1X
[8]Porting Java's ConcurrentHashMap to Rust (part 1) - YouTube:?https://www.youtube.com/watch?v=yQFWmGaFBjk&list=PLqbS7AVVErFj824-6QgnK\_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/datafuselabs/databend
