Rust 編碼
工作流程
-
熟悉專案。 在開始撰寫程式碼前,先閱讀專案的真相來源檔案:
rust-toolchain.toml、Cargo.toml(edition、rust-version、[lints]、[workspace])、clippy.toml、rustfmt.toml、.cargo/config.toml,以及 CI 設定檔、justfile、Makefile或xtask,以了解標準的檢查指令。閱讀你正在修改的模組及其相鄰模組;重複使用它們的輔助函式、錯誤型別和模式。 完成條件: 你能說出版本號 (edition)、最低支援 Rust 版本 (MSRV)、lint 政策、每個disallowed-methods/disallowed-types條目,以及專案的檢查指令。 -
為型別建立模型。 先設計資料,再設計控制流程,遵循型別的原則。若變更函式庫的公開 API:請閱讀
references/api.md。 完成條件: 每個變更所仰賴的不變量 (invariant),都已透過型別編碼,或由一個帶有說明的expect所維護。 -
在小循環中實作。 以小步驟進行,並在每一步後執行
cargo check。閱讀完整的診斷訊息:其help:行通常會指出修正方法,而rustc --explain E0502則會展開錯誤代碼。對於依賴項的 API,請在鎖定的版本 (Cargo.lock) 下,於$CARGO_HOME/registry/src/中閱讀其原始碼。大量重新命名需透過 rust-analyzer 的重新命名功能或ast-grep進行;cargo clippy --fix可套用機器可套用的建議。錯誤修復:先撰寫回歸測試,並觀察它失敗(紅燈)。 完成條件: 變更在你修改的行上能編譯通過且無任何警告,且任何回歸測試在修復前為失敗,修復後為成功。 -
驗證。 執行步驟 1 中的專案指令;否則執行:
cargo fmt --all cargo clippy --workspace --all-targets --all-features -- -D warnings cargo test --workspace --all-features- 對於沒有
[lints]政策的專案:另外執行cargo clippy --all-targets -- -W clippy::pedantic,並修正你修改行上的問題。 - 對於互斥的功能 (features):將
--all-features替換為 CI 測試的每個功能集。 cargo nextest會跳過 doctest:請額外執行cargo test --doc。- 若公開文件已變更:
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features。 - 若觸及
unsafe程式碼:請執行references/unsafe.md。 - 從根源修正 lint。當壓制 (suppression) 是正確做法時,請將
#[expect(clippy::lint_name, reason = "...")]的範圍限定在最小的項目上。 完成條件: 每個指令都回傳 0 退出碼,你修改的行上未引發任何警告,且每個新的壓制都帶有reason。
- 對於沒有
-
自行審查 diff。 重新閱讀完整的
git diff,而不是依賴你的記憶,並對照規則以及 diff 所觸及之每個參考檔案分支的規則進行審查。 完成條件: 每個區塊 (hunk) 都是任務所需,原型程式碼和dbg!/todo!已被移除,文件註解符合行為,且每條適用的規則都適用於每個區塊。
審查他人的程式碼:執行步驟 1,在可以的情況下執行步驟 4 的指令,然後對他們的 diff 套用步驟 5。每個發現應以 path:line、違反的規則以及修正方式回報。
規則
所有權
- 在參數中使用借用:
&str、&[T]、&Path、&T。只有當函式需要儲存或消耗String、Vec<T>或T時,才以值傳遞。 - 每個
.clone()都應有其存在的理由:被呼叫端需要所有權,或者該值是輕量級的擁有權控制代碼 (Arc::clone(&handle))。 - 當借用檢查器提出異議時,先重構:縮短借用範圍、分別借用不相交的 (disjoint) 欄位、使用
std::mem::take/replace、或對Vec使用索引。共享的可變所有權(Rc<RefCell<_>>、Arc<Mutex<_>>)應是最後的解決方案。 - 當呼叫端僅需迭代時,回傳
impl Iterator<Item = T>。
型別
- 讓非法狀態無法表示:使用攜帶每種狀態資料的
enum,而非bool標誌加上Option欄位;為 ID、單位和驗證過的值使用新類型 (newtype)(例如struct Port(NonZeroU16))。 - 解析,而非驗證:在邊界處將不受信任的輸入轉換一次(使用
TryFrom、FromStr、#[serde(try_from = "Raw")]),並將解析後的型別向內傳遞。 - 對你擁有的列舉進行窮舉式 (exhaustive) 匹配。只有在未來的新變體也應走該分支時,才適合使用
_分支。 - 使用
From/TryFrom轉換整數;as會默默地截斷和翻轉正負號。
錯誤與恐慌
- 函式庫:為每個失敗領域建立一個
thiserror列舉。二進位檔:使用anyhow::Result搭配.context(...)。 - 恐慌 (Panic) 意味著錯誤。
unwrap()僅用於測試;生產程式碼使用?,或使用expect("...")來說明使失敗不可能發生的不變量。
當前慣用語
訓練資料中較舊的模式佔多數。當 rust-version 允許時,偏好使用當前形式;新建的 crate 使用 edition = "2024"。clippy::incompatible_msrv 僅能捕捉較新的標準函式庫 API;語言和 Cargo 功能需要使用 cargo +<msrv> check。
| 偏好使用 | 取代 | 自版本起 |
|---|---|---|
std::sync::LazyLock、OnceLock | lazy_static!、once_cell | 1.80、1.70 |
let Some(x) = opt else { return ... }; | match + 提前返回 | 1.65 |
if let Some(x) = a && x > 0 | 巢狀 if let | 1.88、edition 2024 |
在 traits 中使用原生 async fn | #[async_trait](仍需要 dyn Trait) | 1.75 |
| `async | {} 閉包、AsyncFn` 邊界 | |
#[expect(lint, reason = "...")] | #[allow(lint)] | 1.81 |
&raw const x、&raw mut x | ptr::addr_of! | 1.82 |
opt.is_some_and(f)、opt.is_none_or(f) | map_or(false, f)、map_or(true, f) | 1.70、1.82 |
std::hint::black_box | criterion::black_box | 1.66 |
在 Cargo.toml 中使用 [lints] 作為共享政策 | 在每個 crate 根目錄中使用 #![warn(...)] | 1.74 |
cargo:: 建置腳本指令 | cargo: | 1.77 |
文件與註解
- 公開項目需有
///文件:在可能失敗的函式上使用# Errors,在可能恐慌的函式上使用# Panics,使用?將範例作為 doctest。 - 註解解釋原因;程式碼展示方式。
依賴項
- 重複使用工作區已依賴的項目(
[workspace.dependencies]、cargo tree -i <crate>),並在標準函式庫能滿足需求時優先使用它。 - 使用
cargo add新增(注意 MSRV),並僅啟用你使用的功能。
參考資料
載入與你當前分支相符的檔案:
references/api.md:設計或變更函式庫的公開 API、命名、trait 實作、semver。references/errors.md:定義錯誤型別、傳播、恐慌與Result。references/async.md:非同步、tokio、執行緒、通道、鎖、原子操作。references/unsafe.md:unsafe、FFI、原始指標、手動Send/Sync。references/cargo.md:Cargo.toml、工作區、功能、設定檔、建置腳本、crate 選擇。references/lints.md:為 crate 或工作區設定 lint 配置。references/testing.md:撰寫測試、屬性和快照測試、基準測試。references/perf.md:優化已測量到的熱點路徑。