Rust QC
此技能為 Rust 程式碼設定一個標準,並以三種模式應用。規則來自 Rust Style Guide、Rust API Guidelines、Microsoft 的 Pragmatic Rust Guidelines (2026.6)、Google 的 Rust Style Guide,以及 memchr、jiff、ripgrep、fd 和 dua-cli 中可見的實務。當那些來源有分歧時,references/precedence.md 會記錄決策,讓你不必重新爭論。
步驟 1:建立上下文(所有模式)
在撰寫或判斷任何程式碼之前執行此步驟。如果前置條件失敗,則停止並回報;不要猜測缺少的工具鏈或 manifest。
- 找到 crate 根目錄(最近的
Cargo.toml)。注意它是 workspace、library、binary 還是兩者兼具,以及它是否已發佈(publish = false或僅在 workspace 內部使用,或透過 FFI 表示內部)。 - 首先讀取專案自己的規則,因為它們會覆蓋此技能的預設值:
rustfmt.toml或.rustfmt.toml、clippy.toml、Cargo.toml中的[lints]和[workspace.lints]、edition、rust-version(MSRV)、rust-toolchain.toml、CONTRIBUTING.md、AGENTS.md或CLAUDE.md,以及任何AI_POLICY.md。如果AI_POLICY.md或CONTRIBUTING.md限制了 AI 或代理的貢獻,請在產生針對該儲存庫的變更之前告知使用者。 - 將程式碼分類為 library 或 application。API 設計規則對 library 具有約束力,對 application 則是強烈建議,後者應優先考慮直接性而非通用性。
- 注意平台。如果
rust-toolchain.toml列出了目標,或者程式碼包含大量用於非本機平台的#[cfg(windows)]風格的 gate,則也需使用--target <triple>執行 gate(它會針對該目標進行 lint 和文件檢查;測試則跳過)。僅因平台 gate 而出現於本機的警告(例如 dead code)是本機的假象,不是發現的問題。 - 執行一次
scripts/check.sh <crate-root>以記錄基準線。你需要它來區分自己的回歸與既有的技術債務。當 crate 具有互斥的功能時,請使用--features "<list>"來代替預設的--all-features。
除非使用者要求,否則不要安裝工具鏈、元件或工具(nightly、Miri、cargo-hack),也不要使用超出取得 crate 範圍的網路。當某項檢查缺少某個東西時,請說明並推薦它。
步驟 2:選擇模式
建立:當使用者想要新的函式、方法、型別、模組或 crate 時。請遵循下方的「建立程式碼」。
修改:當使用者想要變更現有程式碼時:功能、改良、錯誤修正、重構或稽核發現的補救措施。請遵循下方的「修改程式碼」。
稽核:當使用者想要審查、評估或檢查現有程式碼,找出問題並提出修正時。請遵循下方的「稽核程式碼」。稽核不會變更程式碼,除非使用者要求;它會產生發現的項目。
一個請求可以涵蓋多種模式(「稽核此模組並修正你發現的問題」)。先執行稽核,回報,然後切換到修改模式進行修正。
不可協商的規則
這些規則適用於每種模式。完整的規則集及其 ID 存放在文末列出的參考檔案中。
- 格式化是 rustfmt 的工作。 絕不要手動格式化以對抗 rustfmt。除非是為了故意的對齊(表格、矩陣)或產生的程式碼,否則不要加入
#[rustfmt::skip]。 - 程式碼是健全的。 安全程式碼不得導致未定義行為。每個
unsafe區塊的正上方都必須有// SAFETY:註解,解釋每個前置條件成立的原因。每個unsafe fn和unsafe trait都必須有# Safety文件區段。僅在 FFI、經過基準測試的性能需求或新穎的抽象時使用unsafe,並且永遠不要用它來繞過Send、Sync或生命週期。請參閱references/unsafe.md。 - 錯誤與恐慌。 由輸入或環境造成的可回復失敗應回傳
Result(或當原因明顯時回傳Option)。檢測到的程式設計錯誤則會引發恐慌。Library 程式碼不應因呼叫者的合理輸入而發生恐慌。請參閱references/errors.md。 - Library 錯誤讓呼叫者無需解析文字即可進行分支。 一個具體的錯誤型別,帶有一個回傳公開
#[non_exhaustive]枚舉或is_*()謂詞的kind()方法。私有欄位是正確的;沒有存取器的私有 kind 是一個缺陷。訊息應指向違規的 token 和位置,並且永遠不要複製整個輸入。 - 在正式程式碼中禁止裸
unwrap()。 請使用?、模式匹配或expect("..."),其訊息應說明不變條件成立的原因。在測試中可以使用unwrap()。文件範例應使用?加上隱藏的# Ok::<(), Box<dyn std::error::Error>>(())這一行。 - 每個新的依賴項都是成本,dev-dependencies 也包含在內。 只有在它能消除真正的複雜性或提供你無法合理自行實作的正確性時才加入(Unicode 大小寫摺疊是一個好理由;為一個 100 行的解析器導入屬性測試框架則不是)。在你的報告中說明原因。
- 結構與大小匹配。 不要為一個小函式和它的錯誤型別創建模組;一個模組只有在承載一個獨特的概念、數個項目或數百行程式碼時,才值得建立一個檔案。
- 使用
#[expect(lint, reason = "...")]來覆蓋 lint, 而不是#[allow],這樣過時的覆蓋才能被發現。 - 公開項目都要有文件說明, 第一句話應是一行約 15 個單詞,並且函式應在適用時包含
# Errors、# Panics和# Safety區段。請參閱references/documentation.md。 - 公開型別應實作
Debug。 旨在給人閱讀的型別也應實作Display。 - 註解解釋原因,而不是內容。 如果註解只是重複程式碼,請刪除它。ripgrep 在
crates/core/main.rs中的分配器註解就是典範:決策、原因以及它適用的條件。 - 註解或文件中禁止使用 ASCII 藝術圖表或製表符繪製字元。 用句子描述結構;圖畫對於螢幕閱讀器來說無法閱讀,並且會在程式碼變更時腐化。(團隊慣例,非上游規定。)
- 文件中禁止出現設計日誌內容。 文件描述的是當前行為,而不是程式碼是如何產生的或遵循了哪些指南。
- 測試用於驗證行為。 不要有只重複常數或反映實作分支的測試。
建立程式碼
由外向內工作:先型別和簽名,然後是文件,然後是實作,最後是測試。在函式體之前撰寫文件註解是發現令人困惑的 API 最快的方法。
- 放置它。 將項目放在擁有其概念的模組中;在一個小的 crate 中,這通常是
lib.rs。新模組以其主要型別命名,並且僅在程式碼足以支撐時才建立(ORG-19)。請參閱references/organization.md。 - 使用型別建模。 為容易混淆或具有不變條件的值使用 newtype,並使用能強制執行不變條件的建構子。在呼叫點意義不明確時,使用
enum代替bool參數。優先使用具名字段而非元組。讓無效狀態無法表示。 - 塑造簽名。 只讀取的內容使用借用(
&str、&[T]、&Path、Option<&T>),只有當你儲存或消耗時才取得所有權。在 library 中,當它能減少摩擦時,接受impl AsRef<Path>或impl IntoIterator;在 application 中,保持具體的型別。對於具有數個可選設定的型別,使用 builder 模式,並在build()中進行驗證。請參閱references/api-design.md。 - 命名它。 遵循 RFC 430 的大小寫規則;
as_/to_/into_轉換;new、with_*、try_new建構子;foo()getter。避免模稜兩可的字眼(Manager、Service、Factory、Helper、Util),並且不要與相同介面中既有的意義衝突(NAME-9)。 - 為它加上文件說明,根據
references/documentation.md,包括一個可行的範例(針對公開項目),以及你回傳的任何位置或長度的單位。 - 實作它。 單一用途的函式。在組合子鏈需要條件式或外部狀態時,使用單純的
for迴圈;當組合子能取代手寫邏輯時(any、position、sum)使用它們。在型別推論會讓讀者猜測時,加上型別標註。人類文字應使用 Unicode 感知的處理:不區分大小寫的匹配使用 Unicode 大小寫摺疊,或記錄其限制(API-27)。 - 處理錯誤,根據
references/errors.md:在 library 中使用帶有公開 kind 或謂詞的具體錯誤型別;在 application 中使用帶有.context()的anyhow。 - 測試它,根據
references/testing.md:行內單元測試、tests/下的整合測試,以及不需要新依賴項的廣泛不變條件檢查(列舉短輸入,或針對樸素參考進行種子循環)。 - 驗證,使用下方的 gate。
修改程式碼
目標是以最小的正確變更,讓被觸及的程式碼至少和你發現它時一樣清晰。
- 先閱讀再編輯。 閱讀整個函式、它的呼叫者、它的測試以及相鄰的 API。識別當地的慣例(匯入風格、錯誤型別、文件標題風格例如
# Example與# Examples、行寬、密度、參數和鍵的名稱)並遵循它們,即使此技能的預設值不同。 - 錯誤修正從一個涵蓋錯誤類別的失敗測試開始。 重現回報的輸入,然後加入相同根本原因會破壞的相鄰案例(更多元素、其他排序、組合)。觀察它們失敗,修正根本原因,觀察它們通過。如果專案將回歸測試集中在一個地方(ripgrep 的
tests/regression.rs),請將你的測試也加入那裡。 - 修正原因,而非症狀。 一個新的
unwrap_or_default()、clone()或抑制失敗的早期回傳,除非你能解釋為什麼原始路徑是錯的,否則不算修正。如果原因從程式碼中不明顯,請在修正處加上一行註解。 - 保持精準。 除非被要求,否則不要重新命名、重新排序或重新格式化程式碼。將不相關的問題列出給使用者,而不是默默地修正它們。
- 保持公開 API 和 ABI 的穩定,除非使用者同意破壞性變更。向公開結構體(含有公開欄位)添加欄位、向詳盡的公開枚舉添加變體、添加必要的 trait 方法,或更改 FFI 合約都是破壞性變更。
- 隨著程式碼更新文件和測試。 簽名或行為的變更意味著在同一個變更中更新
# Errors、# Panics、範例和測試。 - 格式化: 如果基準線是 rustfmt 乾淨的,則執行
cargo fmt。如果不是,則只格式化你變更的部分並告知使用者。如果rustfmt.toml使用了僅限 nightly 的選項(穩定版 rustfmt 會警告它們「僅在 nightly 通道中可用」),則專案的標準是cargo +nightly fmt;如果已安裝則使用它,否則不要自動格式化任何內容並說明原因,因為穩定版 rustfmt 會違反專案的設定重寫程式碼。 - 從稽核中補救: 按嚴重性順序修正發現的項目,每個邏輯變更修正一個發現項目,並在你的摘要中命名該發現項目。
- 驗證,使用下方的 gate,並確認你在步驟 1 中檢查的每個目標上,相對於基準線沒有引入新的警告。
稽核程式碼
-
執行
scripts/check.sh --audit <crate-root>(加上根據步驟 1 的--target)。--audit過程會報告:公開 API 的可達性、未標記說明的unsafe、缺少的文件和# Errors/# Panics區段、測試之外的unwrap、沒有理由的#[allow]、dbg!/todo!、glob 匯入以及沒有訊息的斷言。簡潔地報告這些衛生檢查結果:每個類別(文件、Debug、SAFETY 註解、lint 設定)一個 Minor 發現項目,每個都附上計數和最重要的幾個實例。如果 gate 步驟失敗的唯一原因是此處的工具鏈比專案 CI 或rust-toolchain.toml使用的更新,那是工具鏈漂移:一個 Minor 發現項目,而非 Major。 -
執行
python3 -I scripts/audit_scan.py <crate-root>來檢查 clippy 看不到的東西:glob 重新匯出、模稜兩可的型別名稱、ASCII 藝術註解、設計歷史文件、參數表格、裸 TODOs、空的expect訊息以及 library 程式碼中的 print 巨集。透過閱讀程式碼確認每個命中點;排除誤報。 -
以使用者遇到的方式探測行為。 這是在真實 crate 中發現大多數面向使用者錯誤的地方,因此請在此步驟和步驟 5 花費大部分稽核時間。閱讀 crate 的文件、規範和測試,列出它聲稱能處理的輸入功能。對於每個功能,建立一個現有測試未涵蓋的小型真實輸入,從 workspace 外部的臨時 crate 透過公開 API 執行它,並將輸出與使用者應得的輸出進行比較。涵蓋格式允許但 fixtures 跳過的功能:
- 繼承、間接或預設的值
- 重複項
- 可選或隱藏的部分
- 其他工具能容忍的輕微損壞
- 非 ASCII 文字和舊版編碼
- 大型但合法的大小
使用顯示該問題的輸入報告每個確認的問題。
-
處理不受信任的輸入。 對於每個解析不受信任的位元組或文字的公開函式,對相關字母表上的每個短輸入或數百萬個種子隨機突變進行饋送,並捕捉恐慌。同時嘗試在超時情況下測試那些導致工作增長速度快於輸入大小的精心構造結構(共享或遞迴引用、深度巢狀)。
-
閱讀工具無法判斷的程式碼:每個
unsafe區塊的健全性、錯誤設計、API 形狀、模組邊界、命名、測試品質,以及註解是否解釋了原因。在與作業系統互動的程式碼中,也要檢查:- 在系統呼叫、I/O 或環境失敗時發生恐慌(對回傳值使用
assert!或unwrap、非 UTF-8 的環境變數或 passwd 欄位) fork和exec之間的程式碼(pre_exec閉包)或訊號處理常式中分配、鎖定、格式化、記錄或退出的程式碼,這些都不是 async-signal-safe 的- 洩漏的檔案描述符或控制代碼,以及發送到可能已被回收並重用的行程識別碼的訊號
- 對大小、計數和座標進行的窄化轉換和未檢查的算術運算
使用
references/audit-checklist.md。 - 在系統呼叫、I/O 或環境失敗時發生恐慌(對回傳值使用
-
使用下面的格式回報。為每個發現的項目提出具體的修正方案。
步驟 1 和 2 是一個快速的底線,並非完整的稽核:在它們上面花費的時間不應超過總時間的五分之一。一個主要列出 lint、文件和字面問題的稽核可能代表你花費了太多時間在自動化工具上,而忽略了真實的錯誤。步驟 3-5 是稽核的核心工作。
驗證 Gate
在所有模式中,最終的驗證步驟是相同的:
cd <crate-root>
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo doc --no-deps
cargo test
如果是 workspace,則使用 --workspace。如果存在 scripts/check.sh,則使用它(因為它可能包含了額外的步驟,例如 cargo-hack 或 Miri)。如果 ci 腳本使用了不同的 flags(例如 --lib),則重複該 flags。
參考檔案
此技能將詳細規則存放在 references/ 下的獨立檔案中:
references/api-design.mdreferences/audit-checklist.mdreferences/documentation.mdreferences/errors.mdreferences/organization.mdreferences/precedence.mdreferences/testing.mdreferences/unsafe.md
當這些檔案與此 SKILL.md 不一致時,參考檔案具有更高的優先權。