Mục lục
Mục Tiêu Bài Học
Sau bài học, bạn sẽ:
- Hiểu vai trò của
clippynhư linter idiom bổ sung chorustc— phát hiện những thứ compile được nhưng không idiomatic. - Chạy được
cargo clippytrên project, đọc output, hiểu các trường lint name, severity, source span, suggestion. - Phân biệt 6 category chính:
correctness,suspicious,style,complexity,perf,pedantic— biết category nào bật mặc định và category nào phải opt-in. - Sửa được 3 lint kinh điển:
needless_clone,single_match,redundant_closure. - Dùng attribute
#[allow(clippy::...)],#[warn(...)],#[deny(...)]ở mức function, module, hay crate để tinh chỉnh severity của từng lint. - Cấu hình CI với
cargo clippy -- -D warningsđể fail khi có bất kỳ warning nào — phổ biến trên GitHub Actions, GitLab CI. - Dùng
cargo clippy --fixđể tự sửa các lint có suggestion machine-applicable.
clippy Là Gì
clippy là linter chính thức của Rust, host trên repo rust-lang/rust-clippy cùng tổ chức với rustc. Tên lấy cảm hứng từ con kẹp giấy "Clippy" của Microsoft Office — một trợ lý hiện ra để góp ý dù không ai hỏi. Khác con kẹp giấy huyền thoại đó, clippy ở đây thực sự hữu ích: hiện 2026 đã có hơn 600 lint, mỗi cái nhắm vào một anti-pattern hoặc cơ hội cải thiện cụ thể trong code Rust.
Khác biệt với rustc: compiler chỉ warning khi code có vấn đề về correctness ở mức ngôn ngữ — biến không dùng, import thừa, mismatched type, dead code. Còn các thứ kiểu "biểu thức này quá phức tạp", "viết theo idiom Rust sẽ ngắn hơn", "có cách viết nhanh hơn cho cùng kết quả" thì compiler không can thiệp. Clippy lấp đúng khoảng trống đó: là layer suggestion về idiom và performance, complementary với compiler warning.
Vì là tool chính thức, clippy được phân phối qua rustup — không cần cài thêm crate. Component clippy đi kèm toolchain mặc định khi bạn cài rustup-init; nếu thiếu thì rustup component add clippy là xong. Phiên bản clippy luôn pin chặt với phiên bản rustc trong cùng channel để tránh mismatch khi parse cú pháp mới.
cargo clippy Chạy Như Thế Nào
Lệnh tối giản:
cargo clippy
Tương tự cargo check ở chỗ chỉ analyze + typecheck, không link binary — nhanh hơn full build. Khác là sau khi compiler chạy xong, clippy chạy thêm pass riêng quét toàn bộ AST và HIR, áp các lint mà nó biết. Output điển hình:
$ cargo clippy
Checking my-app v0.1.0 (/Users/you/my-app)
warning: redundant clone
--> src/main.rs:8:15
|
8 | let y = x.clone();
| ^^^^^^^^ help: remove this
|
= note: `#[warn(clippy::redundant_clone)]` on by default
= help: for further information visit
https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
warning: `my-app` (bin "my-app") generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s
Đọc output này có 4 phần:
- Tên lint:
clippy::redundant_clone— namespaceclippy::phân biệt với lint củarustc. - Source span: file + line + cột chỉ chính xác chỗ vi phạm.
- Help suggestion: cách clippy đề xuất sửa. Đôi khi là "remove this", đôi khi là replacement đầy đủ.
- Link tài liệu: URL tới trang giải thích lint chi tiết — vì sao có lint này, ví dụ tốt/xấu, exception khi nào nên
#[allow].
Một số flag hữu ích cùng cargo clippy:
cargo clippy --all-targets— lint cả test, example, bench, không chỉ lib/bin.cargo clippy --workspace— chạy trên toàn bộ workspace.cargo clippy --release— chạy với profile release (một số lint chỉ trigger ở config tối ưu).cargo clippy -- -W clippy::pedantic— bật thêm categorypedantic(mặc định off). Lưu ý dấu--ngăn cách arg cargo và arg rustc.
6 Categories Của Lint
Mỗi lint thuộc đúng một category, có severity mặc định riêng:
correctness(default:deny) — code chắc chắn sai, hầu như luôn là bug. Ví dụ:almost_swapped(viếta = b; b = a;quên temp variable). Đây là category bắt buộc — clippy treat như compile error.suspicious(default:warn) — code có thể sai, đa số trường hợp là nhầm. Ví dụ:assign_op_pattern(viếta = a + bthay vìa += b).style(default:warn) — code chạy đúng nhưng không idiomatic, có cách viết ngắn/rõ hơn theo convention community. Ví dụ:single_match,redundant_field_names.complexity(default:warn) — code phức tạp không cần thiết. Ví dụ:needless_return,option_map_unit_fn.perf(default:warn) — code chạy chậm hơn cần thiết. Ví dụ:redundant_clone,or_fun_call(lười evaluate quaor_else).pedantic(default:allow) — lint rất nghiêm khắc, opinionated, đôi khi false-positive cao. Phải opt-in bằng#[warn(clippy::pedantic)]hoặc flag CLI. Ví dụ:must_use_candidate,missing_errors_doc.
Ngoài 6 chính, còn có nursery (lint thử nghiệm chưa stable), cargo (lint cho Cargo.toml), và restriction (lint cấm hẳn một số construct cho codebase đặc thù — vd cấm panic!). Đa số người dùng chỉ cần biết 6 category chính.
Severity mặc định nghĩa là: chạy cargo clippy mà không tùy chỉnh gì, các category correctness, suspicious, style, complexity, perf tự động được kích hoạt ở mức warn (riêng correctness là deny). Pedantic phải bật thủ công.
Ví Dụ Lint Phổ Biến
needless_clone (perf)
Clone không cần thiết — value đã không còn dùng sau đó, có thể move trực tiếp.
// Trước
fn greet(name: String) {
println!("Hello, {}", name);
}
fn main() {
let name = String::from("An");
greet(name.clone()); // warning: redundant clone
}
// Sau
fn main() {
let name = String::from("An");
greet(name); // move trực tiếp, không alloc thừa
}
single_match (style)
match chỉ care đúng 1 nhánh — viết if let ngắn và rõ ý hơn.
// Trước
fn print_some(opt: Option<i32>) {
match opt {
Some(n) => println!("{}", n),
_ => (), // warning: you seem to be trying to use match
} // for an equality check. Consider using if let
}
// Sau
fn print_some(opt: Option<i32>) {
if let Some(n) = opt {
println!("{}", n);
}
}
redundant_closure (complexity)
Closure chỉ wrap function call mà không thêm gì — pass function trực tiếp.
// Trước
let names = vec!["an", "binh", "cuong"];
let upper: Vec<String> = names.iter()
.map(|s| s.to_string()) // warning: redundant closure
.collect();
// Sau
let upper: Vec<String> = names.iter()
.map(ToString::to_string) // pass method path trực tiếp
.collect();
Cả 3 lint trên bật mặc định — không cần config gì, chạy cargo clippy trên codebase có pattern này là thấy ngay.
allow / warn / deny Attribute
Đôi khi bạn đồng ý với 99% suggestion của clippy nhưng 1 case cụ thể là intentional. Dùng attribute để chỉnh severity từng lint ở scope cần thiết:
#[allow(clippy::lint_name)]— tắt lint, không warning.#[warn(clippy::lint_name)]— bật ở mức warning (default cho hầu hết).#[deny(clippy::lint_name)]— coi như error, build fail.#[forbid(clippy::lint_name)]— nhưdenynhưng không thể override bằng#[allow]ở scope con.
Attribute đặt ở 3 scope:
// 1. Trên function — chỉ áp dụng cho function này
#[allow(clippy::too_many_arguments)]
fn build_request(
method: &str, url: &str, header_auth: &str,
header_accept: &str, body: &[u8], timeout_ms: u64,
retry: u32, follow_redirect: bool,
) {
// ...
}
// 2. Trên module — áp dụng cho tất cả item trong module
#[allow(clippy::module_name_repetitions)]
mod parser {
pub struct ParserError;
pub struct ParserResult;
}
// 3. Trên crate (đầu file lib.rs hoặc main.rs) — toàn crate
#![warn(clippy::pedantic)] // bật pedantic toàn crate
#![allow(clippy::module_name_repetitions)]
#![deny(clippy::unwrap_used)] // cấm .unwrap() toàn crate
Quy tắc inheritance: attribute ở scope rộng hơn (crate) là default; scope hẹp hơn (function) override. Một idiom phổ biến: bật pedantic toàn crate, rồi #[allow] những lint pedantic mà team đồng ý "không cần care" — vừa nghiêm khắc vừa thực dụng.
Có thể group nhiều lint trong cùng attribute: #[allow(clippy::needless_clone, clippy::redundant_closure)]. Nếu muốn cấp granular hơn nữa, dùng clippy.toml ở root project — config từng lint với threshold (vd too-many-arguments-threshold = 10) — sẽ bàn sâu trong bài config riêng.
-D warnings Trong CI
Mặc định cargo clippy chỉ in warning rồi exit code 0 — CI vẫn green. Không ai sửa, warning tích lũy hàng trăm, mất ý nghĩa. Giải pháp: ép CI fail khi có bất kỳ warning nào bằng flag -D warnings:
cargo clippy --all-targets --workspace -- -D warnings
Dấu -- tách arg của cargo với arg truyền cho rustc (vì clippy chạy như rustc driver). -D warnings nghĩa là "deny tất cả lint nào đang ở mức warn" — bao gồm cả warning từ rustc lẫn từ clippy. Có warning → exit code khác 0 → CI fail.
Step GitHub Actions điển hình:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: cargo clippy --all-targets --workspace -- -D warnings
Vài lưu ý thực tế:
- Lần đầu áp dụng
-D warningstrên codebase cũ thường lộ ra hàng chục warning tồn đọng. Hai cách xử lý: (a) sửa hết một lần rồi commit; (b) sửa dần, tạm#[allow(...)]ở file/function cụ thể với TODO ticket. Tránh#![allow(clippy::all)]ở crate root — vô hiệu hoá toàn bộ giá trị của clippy. - CI nên chạy clippy tách step với build test, để khi lint fail không phải đợi build xong mới biết.
- Local dev: dùng alias
cargo clánh xạ tớicargo clippy --all-targets -- -D warningstrong~/.cargo/config.tomlđể chạy nhanh trước commit, đồng bộ với CI.
cargo clippy --fix Tự Sửa
Hầu hết lint có suggestion dạng machine-applicable — clippy biết chính xác đoạn code mới sẽ trông như thế nào. Với những lint này, cargo clippy --fix tự sửa luôn:
$ cargo clippy --fix --allow-dirty --allow-staged
Checking my-app v0.1.0
Fixed src/main.rs (2 fixes)
Fixed src/lib.rs (1 fix)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Mặc định --fix yêu cầu working tree clean (không có thay đổi chưa commit) để khi sửa sai bạn có thể git checkout revert. Trong dev flow thực, dùng --allow-dirty và --allow-staged để cho phép sửa khi có thay đổi đang dở.
Lưu ý:
- Không phải lint nào cũng auto-fix được. Lint kiểu "function này quá dài" không có suggestion cụ thể — chỉ in warning, không sửa.
- Một số fix machine-applicable nhưng kéo theo behavior change subtle (vd lint
let_and_returnbỏ binding tạm có thể ảnh hưởng drop order). Luôncargo testsau khi--fix. - Có thể combine với rustfmt:
cargo clippy --fix && cargo fmt— clippy sửa logic, rustfmt format lại layout. Đây là một bước trong pre-commit hook phổ biến. cargo fix(không cóclippy) chỉ fix warning củarustc, không bao gồm lint clippy. Hai tool độc lập.
Tổng Kết
clippylà linter chính thức của Rust, 600+ lint cho idiom + performance, complementary với warning củarustc.- Cài qua
rustup component add clippy; chạy bằngcargo clippytương tựcargo checknhưng có thêm pass lint. - 6 category chính:
correctness(deny mặc định),suspicious,style,complexity,perf(warn mặc định),pedantic(allow mặc định, phải opt-in). - Lint phổ biến:
needless_clone(perf),single_match(style → dùngif let),redundant_closure(complexity → pass function path trực tiếp). - Attribute
#[allow]/#[warn]/#[deny]đặt ở function, module hoặc crate để tinh chỉnh từng lint.#[forbid]không thể override. - CI:
cargo clippy --all-targets --workspace -- -D warningsđể fail khi có bất kỳ warning nào. cargo clippy --fixtự sửa các lint có suggestion machine-applicable. Dùng--allow-dirtytrong dev flow. Luôn chạy test sau khi fix.- Pair với
rustfmt: clippy sửa logic, rustfmt format layout — workflow chuẩn pre-commit.
Bài Tập Củng Cố
Tự trả lời, đáp án ở cuối:
- Vì sao clippy được gọi là "complementary" với
rustcwarning thay vì "thay thế"? Nêu 2 ví dụ thứrustcwarning nhưng clippy không, và 2 ví dụ ngược lại. - Bạn chạy
cargo clippytrên project mới, hoàn toàn không có warning nào hiện ra. Đồng nghiệp khẳng định "code này pedantic chắc chắn có lỗi". Vì sao output rỗng, và bạn làm gì để xác minh? - Function
build_requestnhận 8 tham số (intentional vì API external). Clippy báotoo_many_arguments. Bạn không muốn refactor, không muốn tắt lint toàn crate. Cách giải quyết tinh tế nhất? - Codebase legacy đang có 200 clippy warning. Manager muốn bật
-D warningstrong CI. Đề xuất kế hoạch migration không block PR mới mà vẫn tiến triển dần. - Sau khi chạy
cargo clippy --fix, một test trước đó pass giờ fail. Lý do có thể là gì? Bước debug đầu tiên? - Sự khác biệt giữa
cargo fixvàcargo clippy --fix? Khi nào nên chạy cả hai?
Đáp án
rustcfocus vào correctness ngôn ngữ (type, borrow, unused), không đụng vào idiom/perf. Clippy focus vào idiom + perf, không lặp lại việc của compiler. Ví dụrustcbáo nhưng clippy không:unused_variables,dead_code. Ví dụ clippy báo nhưngrustckhông:needless_clone,single_match. Hai layer độc lập, đều cần.- Vì
pedanticcategory mặc định làallow. Để bật cần thêm#![warn(clippy::pedantic)]ở đầulib.rs/main.rs, hoặc chạycargo clippy -- -W clippy::pedantic. Sau khi bật pedantic, thường lộ ra nhiều lint kiểu thiếu#[must_use], doc thiếu sectionErrors, casting truncate ngầm... - Đặt
#[allow(clippy::too_many_arguments)]ngay trên functionbuild_requestkèm comment giải thích lý do (vd "match shape của API X"). Scope hẹp, không ảnh hưởng các function khác. Nếu pattern lặp lại trên nhiều function, có thể đặt ở mức module thay vì crate. - Plan: (a) thêm step CI chạy
cargo clippynhưng không-D warningsđể theo dõi số warning. (b) Cài cấu hình "warning ratchet" — số warning không được tăng so với baseline trên main branch (có thể implement bằng wrapper script đếm). (c) Sửa dần theo file/module, mỗi PR sửa một phần kèm#![deny(clippy::<lint>)]ở module đã clean để chống regression. (d) Khi tổng warning = 0, bật-D warningschính thức. - Lý do thường gặp: clippy fix thay đổi behavior subtle. Ví dụ
let_and_returnbỏ binding tạm có thể ảnh hưởng drop order nếu binding đó làMutexGuardhoặcFile.collapsible_ifđôi khi rút gọn quá tay làm thay đổi short-circuit semantics. Bước debug:git diffxem fix nào áp dụng, đối chiếu test fail với fix nào ảnh hưởng cùng file, revert fix đó và xác nhận test pass lại — sau đó#[allow]cho đoạn code đó. cargo fixsửa warning của rustc (vd unused import, deprecated syntax migration giữa edition).cargo clippy --fixsửa lint của clippy. Hai tool độc lập, hai tập warning khác nhau. Nên chạy cả hai trong pre-commit hook:cargo fix --allow-dirty && cargo clippy --fix --allow-dirty && cargo fmt— rustc fix trước (sửa import, deprecation), clippy fix tiếp (idiom), rustfmt cuối (layout). Sau đó chạy test để chắc chắn không vỡ.
Bài Tiếp Theo
Bài 270: rust-analyzer — Features Trong VS Code / RustRover — bài tiếp đi sâu vào rust-analyzer, language server chính thức của Rust: các tính năng inlay hint, code action, expand macro, view HIR/MIR, run/debug Codelens, cách tinh chỉnh qua rust-analyzer.toml, và pattern dùng chung với cargo clippy qua setting rust-analyzer.check.command = "clippy" để IDE hiển thị lint clippy ngay trong editor.
