#[deny(...)]
#[deny(lint_name)] (and #![deny(lint_name)]) elevates a lint to a hard compilation error: if the lint triggers anywhere within the annotated scope (unless overridden by a narrower inner #[allow(lint_name)]), compilation fails immediately with a non-zero exit code.
How #[deny] Interacts with Narrower #[allow] Overrides
Unlike #[forbid(...)] (which locks the lint level permanently), #[deny(...)] can still be overridden in an inner scope by #[allow(...)] or #[expect(...)]. This makes #![deny(...)] ideal for enforcing a strict default policy across a crate or module while still permitting explicitly documented, audited exceptions on individual functions:
Crate or Module Policy:
#![deny(clippy::undocumented_unsafe_blocks)]
|
+-- Unannotated `unsafe {}` block ---> Hard Compile Error!
|
+-- Specific legacy function with `#[allow(...)]` ---> Allowed Exception!
Example: Triggering a Hard Error with #[deny(...)]
By default, ignoring a #[must_use] return value (such as a Result) emits the unused_must_use warning. Applying #[deny(unused_must_use)] (or #[deny( warnings )]) turns that warning into a compilation error:
// ❌ does not compile
#[deny(unused_must_use)]
fn process_transaction() {
let res: Result<u32, &str> = Err("insufficient funds");
// Ignored `Result` triggers `unused_must_use`, which is denied here:
res.map(|v| v + 1);
}
fn main() {
process_transaction();
}
error: unused `Result` that must be used
--> src/main.rs:6:5
|
6 | res.map(|v| v + 1);
| ^^^^^^^^^^^^^^^^^^
|
= note: this `Result` may be an `Err` variant, which should be handled
note: the lint level is defined here
--> src/main.rs:2:8
|
2 | #[deny(unused_must_use)]
| ^^^^^^^^^^^^^^^
Audited Local Override Inside a #![deny(...)] Scope
When a crate denies a lint (such as unsafe_code or dead_code), an inner item can explicitly opt out with #[allow(..., reason = "...")]:
#![deny(dead_code)]
pub fn active_entry_point() -> u32 {
42
}
// Even though the crate has `#![deny(dead_code)]`, an inner `#[allow]` can
// override `#![deny]` for a specific item:
#[allow(dead_code, reason = "Kept for upcoming benchmark harness")]
fn benchmark_helper() -> u32 {
active_entry_point() * 2
}
fn main() {
println!("entry point: {}", active_entry_point());
}
entry point: 42
Built-In Lints That Are deny-by-Default in rustc
Several safety and correctness lints are set to deny by default in the compiler (such as static_mut_refs and unsafe_op_in_unsafe_fn in later Rust 2024 lint stages, improper_ctypes_definitions, ambiguous_associated_items, and mutable_transmutes).