Miért volt eddig macera a lint-konfiguráció
Ha dolgoztál már nagyobb Rust workspace-en, biztosan ismerős a jelenet: minden crate lib.rs vagy main.rs fájljának tetején egy csokornyi #![allow(...)] és #![warn(...)] attribútum sorakozik, amit valaki egyszer odaírt, aztán soha többé nem nyúlt hozzá senki. A probléma ezzel több szintű:
- Duplikáció: egy 20 crate-es workspace-ben 20-szor kell ugyanazt a
#![deny(clippy::unwrap_used)]sort bemásolni. - Inkonzisztencia: az egyik crate-ben elfelejtik a lintet, a másikban máshogy állítják be a szintjét (
warnvsdeny). - Láthatóság: a lint-beállítások a forráskód belsejében bujkálnak, nem a projekt metaadatai között, ahol a Cargo.toml már most is otthont ad a dependency-knek, feature flageknek és a build konfigurációnak.
- CI-fájdalom: ha a CI-ban külön
RUSTFLAGSvagyclippy.tomlállít be lint-szinteket, könnyen szétcsúszik a lokális és a CI-s viselkedés.
A clippy.toml fájl már régóta létezik, de az csak bizonyos lintek paramétereit (pl. küszöbértékeket) tudja finomhangolni, a lintek be- és kikapcsolását nem helyettesíti.
Ez a fájdalom vezetett el ahhoz, hogy a Cargo csapata elkezdett dolgozni egy natív megoldáson, amit közvetlenül a Cargo.toml-ba lehet írni.
Mi újság: a [lints] szekció (jelenleg beta)
A jelenlegi stabil Rust verzió 1.73, amiben a [lints] szekció még nem áll rendelkezésre stabil csatornán – ez a funkció épp a beta ágon fut, és a következő, 1.74-es release-ben várható stabilizálva. Ha ki akarod próbálni már most, a beta toolchain-t kell használnod:
rustup install beta
cargo +beta build
Amíg nem jelenik meg a stabil 1.74, a [lints] szekció használatához explicit módon be kell kapcsolnod egy cargo-features sort a workspace root Cargo.toml-jában, és a +beta toolchain-t kell futtatnod. Ez a cikk ezt a beta viselkedést mutatja be – stabil release-en még hibát fog dobni.
A szintaxis lényege, hogy a [lints] szekció alá bontva kategóriánként (rust, clippy, rustdoc) adhatod meg a lint neveket és a hozzájuk tartozó szintet:
[lints.rust]
unsafe_code = "forbid"
unused_variables = "warn"
[lints.clippy]
all = "warn"
pedantic = "warn"
unwrap_used = "deny"
Ez pontosan azt jelenti, amit eddig #![forbid(unsafe_code)], #![warn(unused_variables)] stb. formában írtál volna a kód elejére – csak most a projekt metaadatai közé kerül, ahol a build eszköz (Cargo) natívan tudja kezelni és validálni.
A lint szintek, amiket megadhatsz, ugyanazok, mint amiket a Rust attribútumoknál is használsz:
"allow"– csendben elnémítja"warn"– figyelmeztet, de nem bukik el a build"deny"– hibává emeli a lintet"forbid"– hiba, és nem lehet lokálisan felülírniallow-val
Workspace-szintű lint öröklés
Ami igazán megváltoztatja a játékot, az a workspace-szintű öröklés. Ha egy Cargo workspace-ben dolgozol több crate-tel, a root Cargo.toml-ban definiálhatod a közös lint-szabályokat, majd minden tag crate egyszerűen örökölheti azokat:
# workspace root Cargo.toml
[workspace.lints.rust]
unsafe_code = "forbid"
[workspace.lints.clippy]
all = "warn"
unwrap_used = "deny"
expect_used = "deny"
Ezután minden crate saját Cargo.toml-jában csak ennyit kell írnod:
[lints]
workspace = true
Ez azonnal megoldja a duplikációs problémát: egy helyen módosítod a szabályt, és minden crate azonnal érvényesíti. Ha egy adott crate-ben mégis felül akarsz írni valamit (mondjuk egy legacy modulban engedni kell az unwrap-et), akkor a crate saját [lints] szekciójában finomíthatod:
[lints]
workspace = true
[lints.clippy]
unwrap_used = "allow" # csak ebben a crate-ben
A workspace-öröklés különösen jól jön nagy monorepókban, ahol 15-20 crate van egy Cargo.toml alatt. Egyetlen helyen tartod karban a lint-politikát, és a CI konfigurációdból is törölhetsz egy csomó RUSTFLAGS hackelést.
Clippy lintek testreszabása gyakorlatban
A Clippy lintek finomhangolása az, ahol ez a funkció igazán hasznossá válik. Nézzünk néhány konkrét, gyakran hasznos beállítást.
Szigorú hibakezelés kikényszerítése – ha szeretnéd elkerülni, hogy valaki unwrap()-et vagy expect()-et hagyjon a production kódban:
[lints.clippy]
unwrap_used = "deny"
expect_used = "warn"
panic = "warn"
A pedantic és nursery csoportok bevonása – ezek alapból ki vannak kapcsolva, mert szigorúbbak és több false positive-ot adhatnak, de sokat segítenek a kód minőségén:
[lints.clippy]
all = "warn"
pedantic = "warn"
nursery = "warn"
# Egy konkrét pedantic lint, amivel nem egyezünk
module_name_repetitions = "allow"
Rustdoc lintek – ha fontos, hogy a publikus API-d dokumentációja hiánytalan legyen:
[lints.rustdoc]
broken_intra_doc_links = "deny"
missing_crate_level_docs = "warn"
Egy tipikus, valós projektben így nézhet ki egy összeszedett, jól karbantartott lint-blokk:
[workspace.lints.rust]
unsafe_code = "forbid"
missing_docs = "warn"
[workspace.lints.clippy]
all = "warn"
pedantic = "warn"
unwrap_used = "deny"
expect_used = "deny"
todo = "warn"
dbg_macro = "deny"
Ha a pedantic csoportot élesben bekapcsolod egy már meglévő, nagy kódbázison, számíts arra, hogy elsőre több száz figyelmeztetést kapsz. Érdemes fokozatosan bevezetni, crate-enként, ne egyszerre a teljes workspace-re.
Az olyan modernebb nyelvi elemek, mint a let-else mintaillesztés vagy a OnceCell/OnceLock inicializációs mintázatok, jól passzolnak a szigorúbb Clippy-beállításokhoz – például a clippy::option_if_let_else lint gyakran pont arra figyelmeztet, hogy egy if let helyett érdemes lenne let-else-t használni, ami olvashatóbb korai kilépést biztosít hibás esetekben:
fn parse_config(input: &str) -> Result<u32, String> {
let Ok(value) = input.trim().parse::<u32>() else {
return Err(format!("nem sikerült parse-olni: {input}"));
};
Ok(value)
}
Migrációs tippek régi #![allow] attribútumokról
Ha már van egy csomó #![allow(...)] attribútumod a kódban, a migráció viszonylag egyszerű, de van néhány trükk, amivel gördülékenyebbé teheted.
1. Gyűjtsd össze a jelenlegi attribútumokat. Nézd át a crate gyökér fájljait (lib.rs, main.rs), és listázd ki minden #![allow], #![warn], #![deny], #![forbid] sort.
2. Másold át kategóriánként. A #![allow(clippy::too_many_arguments)] a Cargo.toml-ban így alakul:
[lints.clippy]
too_many_arguments = "allow"
3. Ne törd le a régi attribútumokat egyszerre. Amíg stabil release-en dolgozol (jelen pillanatban 1.73), a [lints] szekció még nem működik – tehát a kódban lévő attribútumokat egyelőre hagyd meg, és csak akkor cseréld le, amikor a projekt MSRV-je (minimum supported Rust version) eléri az 1.74-et vagy újabbat.
4. Figyelj a lokális felülírásokra. Ha egy adott függvény felett van egy specifikus #[allow(clippy::foo)], azt nem kell (és nem is lehet) a Cargo.toml-ba tenni – a [lints] szekció crate-szintű alapértelmezést állít be, a kódbeli, függvény- vagy modulszintű felülírások továbbra is a forráskódban maradnak és érvényesek.
// Ez a specifikus, lokális felülírás a kódban marad,
// még akkor is, ha a Cargo.toml-ban deny van rá beállítva.
#[allow(clippy::too_many_arguments)]
fn legacy_constructor(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8, g: u8) -> u8 {
a + b + c + d + e + f + g
}
5. Tesztelj a beta csatornán előbb. Mivel a funkció még nem stabil, célszerű egy külön CI job-ot futtatni cargo +beta clippy paranccsal, hogy időben lásd, mennyi munka lesz a teljes átállás, mire az 1.74 stabillá válik.
Ha GATs-ot (Generic Associated Types) használó generikus trait-eket írsz, érdemes a clippy::type_complexity lintet külön figyelni – a bonyolultabb associated type deklarációk gyakran triggerelik, és a Cargo.toml-ban egy sorban tudod máshogy skálázni a küszöböt a clippy.toml fájlon keresztül, miközben a be/kikapcsolást a [lints] szekció intézi.
Összefoglalás
A lint-konfiguráció Cargo.toml-ba emelése apró, de annál fontosabb lépés a Rust tooling érettségében. Megszünteti a szétforgácsolt #![allow] listákat, natívan támogatja a workspace-szintű öröklést, és végre egy helyen, deklaratívan tartod karban a projekted lint-politikáját. Jelen pillanatban ez a funkció a beta csatornán érhető el, a stabil 1.74-es release-ben válik majd elérhetővé – de ha most kezdesz egy új projektet, vagy tervezed a következő nagyobb refaktort, érdemes már most beta toolchain-en kísérletezni vele, hogy felkészülten várd a stabilizálást.