Compiler Lints and Attributes
Compiler lints are static analysis checks performed by rustc, clippy, and rustdoc to catch bugs, performance pitfalls, unidiomatic patterns, and future edition incompatibilities. Lints can be configured at the crate, module, or item level via attributes (#[allow], #[expect], #[warn], #[deny], #[forbid]) or workspace-wide via the [lints] table in Cargo.toml.
Built-In Lint Groups in rustc
Instead of listing dozens of individual lint names, rustc groups related lints under collective lint group names:
| Lint Group | Included Lints / Purpose |
|---|---|
warnings |
Matches every lint currently set to the warn level (#[deny(warnings)] turns all warnings into errors) |
unused |
unused_variables, unused_imports, unused_mut, dead_code, unused_must_use, unused_assignments |
nonstandard_style |
non_camel_case_types, non_snake_case, non_upper_case_globals |
future_incompatible |
Lints warning about patterns that will stop compiling in a future Rust release or edition |
rust_2018_idioms |
Explicit '_ lifetimes, extern crate cleanup |
rust_2024_compatibility |
Lints preparing code for the Rust 2024 edition (unsafe_op_in_unsafe_fn, static_mut_refs, tail_expr_drop_order) |
Centralizing Lint Configuration in Cargo.toml ([lints])
Stabilized in Rust 1.74, the [lints] table in Cargo.toml configures both rustc and clippy lints across all targets (lib, bin, tests, examples) without repeating #![warn(...)] headers in every root .rs file. In a multi-crate Cargo workspace, child crates inherit the workspace lints with [lints] workspace = true:
# Root Cargo.toml
[workspace.lints.rust]
unsafe_code = "forbid"
missing_debug_implementations = "warn"
unused_qualifications = "warn"
[workspace.lints.clippy]
all = { level = "warn", priority = -1 }
undocumented_unsafe_blocks = "deny"
dbg_macro = "warn"
todo = "warn"
# In member crate Cargo.toml:
[lints]
workspace = true
Summary of Core Compiler Attributes Across Categories
| Category | Attributes | Primary Role |
|---|---|---|
| Conditional Compilation | #[cfg(...)], #[cfg_attr(...)] |
Strips or attaches items/attributes based on target or feature flags |
| Derive & Codegen | #[derive(...)], #[inline], #[cold], #[track_caller], #[target_feature] |
Generates trait impls, controls inlining/branch weights, and propagates caller locations |
| Memory Layout & FFI | #[repr(...)], #[unsafe(no_mangle)], #[unsafe(export_name = "...")], #[unsafe(link_section = "...")] |
Controls struct/enum layout and linker symbol names |
| API Evolution & Safety | #[must_use], #[non_exhaustive], #[deprecated] |
Enforces return-value inspection, extensible matching/construction, and migration notices |
| Diagnostics & Lints | #[allow], #[expect], #[warn], #[deny], #[forbid], #[diagnostic::on_unimplemented] |
Tunes compiler/Clippy lints and customizes trait-bound error messages |
| Testing & Docs | #[test], #[ignore], #[should_panic], #[doc(...)] |
Configures unit tests and rustdoc HTML generation |
Customizing Trait Error Diagnostics with #[diagnostic::on_unimplemented]
Stabilized in Rust 1.78, the #[diagnostic::on_unimplemented(...)] attribute allows library authors to customize the exact compiler error message, label, and help notes shown when a caller passes a type T that fails to implement a trait:
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be serialized as a database primary key",
label = "this type is not a valid primary key",
note = "only `u64`, `i64`, and `Uuid` implement `PrimaryKey`"
)]
pub trait PrimaryKey {
fn key_bytes(&self) -> [u8; 8];
}
impl PrimaryKey for u64 {
fn key_bytes(&self) -> [u8; 8] {
self.to_be_bytes()
}
}
fn lookup_by_key<K: PrimaryKey>(key: K) -> [u8; 8] {
key.key_bytes()
}
fn main() {
println!("key bytes: {:02x?}", lookup_by_key(42_u64));
}
key bytes: [00, 00, 00, 00, 00, 00, 00, 2a]