2021年,一位同事告訴我Rust是"能在bug出生前就消滅它們的語言"。我當時信了。三年后,我在自己的生產代碼里找到了它承諾不會存在的bug——而且不止一個。
這不是黑Rust。這是關于我們怎么被"內存安全=邏輯安全"的等式誤導了。
![]()
從HN熱帖到代碼審計
那個HN帖"Bugs Rust won't catch"拿到648贊時,我正需要分心。我打開三個項目:一個純Rust寫的CLI工具,兩個依賴Rust工具鏈的項目。帶著帖子里列的四類bug清單,我逐行檢查。
結果:清單上的每一類,我都找到了實例。
先澄清一件事。Rust的borrow checker確實消除了整類內存錯誤。null pointer dereference、use-after-free、data race——這些在編譯期就被攔截。我的代碼里沒有這些。
但我要找的是另一類東西:編譯器笑著放行的邏輯錯誤。
第一個案例:越界循環的沉默
這是我的配置處理工具里的函數:
// 遍歷元素計算"下一個"索引 // 編譯器完全不知道這個范圍有問題 fn procesar_ventana(datos: &[u32]) -> Vec { let mut resultado = Vec::new(); // Bug: 應該寫成 datos.len() - 1 來配對 // Rust編譯通過,沒有未定義行為,沒有內存錯誤 // 純邏輯錯誤,輸出錯誤結果 for i in 0..datos.len() { if i + 1 < datos.len() { resultado.push(datos[i] + datos[i + 1]); } } resultado }
編譯器零警告。所有內存訪問都合法。但業務邏輯錯了——我需要對比規格文檔才發現。
這讓我想起測TypeScript 7 beta時的經歷:最耗時的錯誤不是編譯器拒絕的,而是它熱情接受、但語義完全錯誤的那種。不同語言,同一陷阱。
算術溢出:當安全成為盲區
Rust在debug模式會panic的整數溢出,在release模式默默回繞。我的數據處理管道里有這個:
fn calcular_checksum(bytes: &[u8]) -> u32 { let mut sum: u32 = 0; for &b in bytes { sum += b as u32; // 大文件會溢出 } sum }
測試用小文件,一切正常。生產環境遇到GB級日志,checksum開始碰撞。Rust沒報錯——這不是內存安全問題,是算術問題。
我后來加了顯式檢查,但關鍵是:我原本以為"Rust會告訴我",結果沒有。
并發邏輯:死鎖不是內存錯誤
我的Rust項目用了async/await。這是簡化后的模式:
async fn procesar_pedido(id: u64) -> Result<(), Error> { let db = POOL.get().await?; // 獲取連接 let cache = CACHE.lock().await; // 獲取鎖 // ... 業務邏輯 ... Ok(()) }
兩個await,兩個資源獲取點。順序錯了就是死鎖。Rust編譯器檢查的是"你有沒有await unsafe代碼",不是"你的鎖順序會不會導致循環等待"。
我在staging環境撞上過一次。日志顯示兩個請求互相持有對方需要的鎖。內存完全安全——沒有data race,沒有use-after-free——但服務卡死了。
序列化假設:類型對的,數據錯的
這是我最隱蔽的一個。用serde解析外部API響應:
#[derive(Deserialize)] struct RespuestaApi { codigo: u32, exito: bool, // 文檔說總是存在 datos: Vec, }
第三方API某天返回了{"codigo": 200, "datos": []},沒有exito字段。serde報錯?不,它用了默認值false。我的代碼繼續跑,基于"請求失敗"的假設走了錯誤分支。
類型系統滿意了:bool有值,Vec是空的但不是null。業務邏輯崩潰了:我以為API返回失敗,其實它成功了只是沒數據。
我在賣什么,我在買什么
審計完三個倉庫,我數了數:7個邏輯錯誤,0個內存安全錯誤。這個數字本身說明不了什么——我的樣本太小,我的Rust經驗不如Java深。
但它印證了我的核心觀察:社區話語里,"內存安全"和"無bug"被悄悄等同了。
borrow checker是工程奇跡。它消除了C/C++代碼里整類難以調試的崩潰。但如果你以為換成Rust就能少寫測試、少做代碼審查、少擔心邊界條件——我的代碼說,不會。
那些bug在Java里也會存在。區別在于,Java我不會期待編譯器幫我抓它們。Rust讓我產生了這種期待,然后部分落空。
工具鏈里的Rust:問題放大器
兩個依賴Rust工具的項目給了我另一個視角。
一個是用ripgrep做日志分析的管道。ripgrep本身沒bug,但我的正則模式有。當輸入包含特定UTF-8序列時,我的捕獲組邏輯錯位,輸出統計完全錯誤。ripgrep快得像閃電,快讓我忽略了結果可能是錯的。
另一個用wasm-pack編譯的瀏覽器擴展。生成的綁定代碼類型正確,但我在JS端傳錯了參數順序——兩個都是string,編譯器沒意見,運行時行為詭異。
Rust工具鏈的輸出是"安全的",但安全不保證正確。
那我們還應該用Rust嗎
用。但帶著正確的預期。
我的CLI工具現在還在生產跑。那個越界循環我修了,加了property-based測試。算術溢出用了checked_add。并發鎖順序文檔化,靜態分析工具掃描潛在死鎖。
Rust給了我基礎:在這些修改之上,我不擔心引入內存腐敗。這是真實的價值。但它沒給我上層建筑——邏輯正確性仍然是我的責任。
那個2021年的同事,我現在想問他:他說的"消滅bug",具體是哪些bug?我們當時沒追問,因為聽起來太美好。
三年后我的追問是:當一種語言的安全承諾被過度推銷時,開發者會低估哪些風險?你的代碼審計清單上,邏輯錯誤和內存錯誤各占多少行?
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.