[TOOLS] 10 分鐘閱讀OraCore 編輯部

三步把 PostgreSQL C 改成 Rust

先机械翻译成能跑的 Rust,再拆 crate,最后让 Claude 逐个重写并审计。

分享 LinkedIn
三步把 PostgreSQL C 改成 Rust

以前我总想一口气重写整坨 C,现在我只先让它活下来,再慢慢变成像样的 Rust

我最近一直盯着这种老 C 项目往 Rust 搬的做法,看久了只觉得一件事:很多人不是输在技术,是输在顺序。你一上来就想把整个系统改成“优雅的 Rust”,测试没补、边界没拆、风险没切开,最后只会把自己埋进一堆半成品里。我看 PostgreSQL 这条路,最有意思的地方就是它承认现实很脏,先把代码搬过去,再谈变漂亮。

这次让我停下来的是这篇 知乎整理文,它转述的是 HN 上作者对 PostgreSQL 第二版 Rust 重写路线的说明。原始工具链也很明确:c2rust 先做机械翻译,Claude 再逐个 crate 改写和审计。这里没有花活,只有流程。

先别重写,先把行为搬过去

訂閱 AI 趨勢週報

每週精選模型發布、工具應用與深度分析,直送信箱。不定期,不騷擾。

不會寄垃圾信,隨時可取消。

先用 c2rust 把 PostgreSQL 的 C 源码机械翻译成 Rust,先得到一份能跑核心 SQL 回归测试的代码。

翻譯一下就是:第一步不是写出“漂亮 Rust”,而是先拿到一份能编译、能跑测试、能继续改的 Rust。这个顺序我很買單。大型系統最怕的不是程式碼醜,而是你根本不知道自己改壞了什麼。先把行為搬過去,至少你知道新舊版本在做同一件事。

三步把 PostgreSQL C 改成 Rust

我以前幹過一次遺留系統遷移,最慘的地方就是團隊把“順手重構”當成附加價值。結果每個人都在改命名、改結構、改錯誤流,最後沒人說得清楚到底是語言遷移失敗,還是業務邏輯被改爛。後來我學乖了:先讓它活,再讓它好看。

c2rust 的價值就在這裡。它不是幫你完成終局,而是幫你跨過 C 到 Rust 的語義鴻溝。對 PostgreSQL 這種體量,靠人手從零重寫根本不現實。你需要的是一份可編譯、可測試、可逐步修補的底稿。

  • 先把能跑當第一目標。
  • 遷移和重構分開做,不要混在一起。
  • 測試先保住,尤其是回歸測試。

如果你自己的專案也在搬家,我會先找工具把舊語言變成新語言的“可編譯版本”。哪怕很醜也沒差,先把行為對齊。接著立刻補測試,先抓輸入輸出穩定、邊界多的地方。別急著刪 unsafe,也別急著美化架構,先確認新舊行為一致。

拆成一千個 crate 不是炫技,是切風險

第二個讓我點頭的地方,是它把 PostgreSQL 拆成大約一千個 crate。這數字聽起來很誇張,但我完全不意外。大系統遷移最怕的不是程式碼多,是邊界糊。邊界一糊,AI 會開始亂補,你也會開始不敢動。

拆 crate 的本質,是把責任切細。每個 crate 有自己的依賴、自己的介面、自己的審計範圍。這樣 Claude 不是在“理解整個 PostgreSQL”,而是在處理一個有限的小盒子。這差很多。模型最擅長的是局部上下文,最怕的是跨太多抽象層。

我看過太多“讓 AI 重寫整個 repo”的 demo,前十分鐘都很爽,後面就開始在命名、錯誤傳遞、生命週期、內部 API 之間撞牆。不是模型太差,是輸入太大。你把一坨泥丟給它,它只會回你一坨比較整齊的泥。

這裡真正成熟的地方,是 crate 拆開後,重寫就變成流水線,不是全局事件。每個單元都能獨立改、獨立審、獨立回退。失敗不會污染整個倉庫,這才叫能持續推進。

  • 邊界越清楚,AI 越能工作。
  • 小單元重寫比整倉重寫更容易審。
  • 可回退、可重試,才像工程。

你可以直接照做:先把 repo 按功能切成最小可管理單元,不要先追求優雅分層。重點是每塊都能獨立編譯、獨立測試。然後把 AI 任務固定在單一模組,例如“只重寫這個 parser 子模組的錯誤處理”,不要丟給它“把整個資料庫核心改成 Rust 風格”。

Claude 只是手,流程才是主角

流程只有三步:挑下一個 crate、重寫、審計。

也就是說,AI 不是架構師,也不是專案經理,它只是執行器。真正決定節奏的,還是人。先改哪個 crate、怎麼改、改完怎麼驗,都得先定好。這種流程很樸素,但我覺得比那種“讓 agent 自己決定下一步”的說法可靠太多。

三步把 PostgreSQL C 改成 Rust

我一直對自動代理那套話術很警惕。聽起來很猛,落地很容易失控。尤其像 PostgreSQL 這種系統,錯誤不是“輸出不夠漂亮”,而是“行為變了但沒人發現”。所以你必須把模型塞進窄流程:輸入明確、輸出明確、審計明確。

這裡的重寫也不是讓 Claude 從零發明實作,而是在機械翻譯的基礎上做局部整理。換句話說,模型的任務是把“能跑但很 C 的 Rust”往更像樣的 Rust 推。它不是創造新語義,而是在既有語義上修整。

審計這一步不能省。因為 AI 改碼最常見的坑,不是編譯失敗,而是語義偷偷漂移。編譯過了,不代表邏輯對了。審計要看的就是:有沒有改到行為、有沒有擴大 unsafe、有沒有把原本清楚的狀態機弄亂。

我會把這種任務寫成固定模板:

  • 輸入:一個 crate 或一個模組。
  • 目標:把機械翻譯碼改得更 idiomatic。
  • 約束:不能改公開行為,不能擴大介面面積。
  • 輸出:補丁、說明、風險點。

這樣做的好處很直接:模型不需要懂整個專案,只要完成一個可驗收的小任務。對大規模遷移來說,這才是能跑的方式。

parser 這種地方,最不適合硬裝聰明

原文提到,像 parser 這類少數由工具生成的模組,仍然保留了大量機械翻譯碼和 unsafe。這細節很重要。不是所有模組都適合被 AI 一口氣重寫。有些地方跟底層資料結構、位元運算、狀態機、生成器綁得死死的,能動的空間本來就小。

parser 這種東西通常有三層麻煩:語義密、邊界多、你一改就容易影響整個系統的輸入相容性。對資料庫來說,解析器不是普通業務碼,它決定 SQL 到底怎麼被理解。你亂改,後面執行器、最佳化器、回歸測試全都會一起炸。

我看過不少團隊在這裡犯同一種錯:覺得“都搬到 Rust 了,那 parser 也順便現代化一下吧”。結果現代化完,語法相容性掉了,測試集炸了,最後還是得回頭補。資料庫最怕的就是看起來更乾淨,但行為不再可信。

所以保留機械翻譯和 unsafe,不一定是偷懶,可能是理性選擇。你不能對所有模組一視同仁。能重構的地方就重構,必須保守的地方就保守。工程上最貴的不是髒碼,是錯誤自信。

如果你也在搬系統,我會先把模組分三類:

  • 可大改:純業務邏輯、邊界清楚、測試高。
  • 可小改:複雜但可局部驗證。
  • 別亂動:協議解析、狀態機、生成碼、核心相容層。

第三類的目標不是“寫得漂亮”,而是“別動壞”。這句話很沒勁,但真的救命。

真正的成本在審計,不在生成

很多人看到 AI 寫程式,第一反應都是“哇,生成好快”。但我做工程越久,越覺得生成從來不是最貴的,審計才是。你讓模型改一千個 crate,真正吃時間的是你得一個一個確認:這次改動是不是安全、是不是等價、是不是引入新依賴和新風險。

這也是我喜歡這套流程的原因。它承認一件很現實的事:模型可以幫你搬磚,但不能替你擔責。審計還是得人來,測試還是得跑,回歸還是得過。沒有這些,AI 只是在放大不確定性。

我自己最在意的是節奏。這種流程不用等整個專案完工才知道成不成,而是每改一個 crate,就能看到結果。這個回饋閉環很重要。它會讓團隊很快知道哪些模組適合繼續交給模型,哪些模組必須人工接手。

如果你想把這套方法用到別的語言遷移上,記住一條:讓 AI 做重複勞動,讓人做風險判斷。重複勞動包括機械改名、樣板整理、局部風格統一;風險判斷包括 API 相容、語義保持、邊界條件、效能回退。別把這兩件事混在一起。

我會固定一份審計清單:

  • 編譯是否通過。
  • 原有測試是否通過。
  • 是否新增 unsafe 或擴大 unsafe 範圍。
  • 是否改變公開介面或行為。
  • 是否引入難以回退的結構性變化。

這套方法適合誰,不適合誰

我不覺得這套方法適合所有專案。它特別適合那種已經有大量測試、模組邊界還能拆、舊行為相對穩定的大系統。PostgreSQL 顯然符合這些前提。它不適合那種需求天天變、測試很薄、介面還在晃的專案。那種專案你先別談遷移,先把產品定義弄清楚。

如果你的倉庫很小,直接重寫也許更快。但只要系統一大,直接重寫就會變成災難。因為你不可能靠一次性腦內模擬,準確複製幾十萬行程式的邊緣行為。這時候,機械翻譯加局部重寫加審計,才是能走下去的路。

我特別想強調:這不是“AI 取代工程師”,而是“AI 讓工程師能把舊系統拆開處理”。差很多。前者是幻想,後者是流程設計。前者只會讓你寫簡報,後者會讓你交付程式。

所以我看完這條路線,最大的感受不是“AI 真猛”,而是“終於有人把遷移這件事講人話了”。先活下來,再變好看;先局部可控,再全局優化。這個順序,真的不能反。

可抄的模板

# 大型 C 到 Rust 遷移工作流模板

## 目標
把一個已有測試的大型 C 專案遷移到 Rust。

## 原則
1. 先保行為,再談風格。
2. 先機械翻譯,再局部重寫。
3. 任何 AI 改動都必須可審計、可回退。
4. 核心模組優先保證測試通過,不強求一次性消滅 unsafe。

## 工作流
### 第 1 步:機械翻譯
- 使用 c2rust 或同類工具把 C 程式翻成 Rust。
- 允許生成大量 unsafe 和非 idiomatic 程式碼。
- 目標是讓程式先編譯、先跑測試。

### 第 2 步:拆分模組
- 按功能把倉庫拆成盡量小的 crate / module。
- 每個單元都要能獨立編譯、獨立測試。
- 對 parser、協議層、狀態機等高風險模組單獨標記。

### 第 3 步:逐個重寫
- 每次只選一個 crate。
- 讓 AI 僅在該 crate 範圍內改寫。
- 目標:更 idiomatic 的 Rust、減少樣板、縮小 unsafe 範圍。

### 第 4 步:人工審計
- 檢查編譯結果。
- 跑原有回歸測試。
- 核對公開行為、錯誤處理、邊界條件。
- 確認沒有引入新的不可回退結構。

## 給 AI 的提示詞
你是資深 Rust 工程師。

任務:重寫目前 crate 中的機械翻譯程式碼,使其更符合 Rust 習慣寫法。

約束:
- 不得改變公開行為。
- 不得擴大 unsafe 範圍。
- 不得修改 crate 之外的程式碼。
- 保持現有測試全部通過。
- 如果發現語義不清楚,先列出風險點,不要猜。

輸出要求:
1. 給出修改後的程式碼。
2. 列出你認為有風險的地方。
3. 說明哪些地方保留了機械翻譯實作,以及原因。

## 審計清單
- [ ] 編譯通過
- [ ] 回歸測試通過
- [ ] 行為未變
- [ ] unsafe 未擴大
- [ ] 模組邊界未破壞
- [ ] 風險點已記錄

## 適用場景
- 大型遺留 C 專案
- 有穩定測試集的系統
- 需要漸進遷移,而不是一次性重寫的專案

這份模板的重點不是照抄 PostgreSQL 的規模,而是把遷移拆成可驗證的小步。你可以按自己的倉庫大小縮放粒度,但核心思路不變:先翻譯,後重寫;先局部,後全局;先審計,後慶祝。

原始來源是 這篇知乎整理文,它轉述了 HN 上關於 PostgreSQL Rust 重寫路線的討論;我這篇的拆解是基於該文內容重新整理,模板則是我自己按這個流程整理出來的。