R
RUSTY YELLOW PAGES

#[allow(...)] / #[warn(...)] / #[deny(...)] / #[forbid(...)]Attribute


Explanation

#[allow(...)], #[warn(...)], #[deny(...)], and #[forbid(...)] are one family: four levels of increasing severity, each taking one or more lint names and applying that level to those lints for the item the attribute decorates (and everything nested inside it, unless a narrower inner scope overrides the level for itself). Every specific named lint — dead_code, unused_variables, missing_docs, a Clippy lint like clippy::needless_clone, and hundreds more — can be assigned any of these four levels:

  • allow — silences the lint entirely for the annotated scope, even if it would otherwise fire. #[allow(dead_code)] on a function that genuinely isn't called yet (a stub for work in progress, say) suppresses the warning without deleting the function.
  • warn — the level most lints already have by default: a warning is printed, but compilation still succeeds. Explicitly writing #[warn(...)] is mostly useful to raise a lint that defaults to allow up to visible, or to restate warn inside a scope that an enclosing deny would otherwise apply to.
  • deny — turns the named lint into a hard compile error for the annotated scope: code that would merely warn now fails to build at all.
  • forbid — everything deny does, plus it prevents any nested scope from lowering the lint back down to allow or warn — an inner #[allow(some_lint)] inside a #[forbid(some_lint)] scope is itself a compile error, rather than silently taking effect. This is the strongest level: a forbidden lint cannot be locally reopened anywhere beneath it.

All four apply the same way whether written as an outer attribute on one item (#[deny(unused_must_use)] above a single function) or an inner attribute at a crate root (#![deny(unused_must_use)], applying crate-wide) — see #[meta] / #![meta] for that general outer/inner distinction.

The command line does the same job, without editing source. rustc/cargo build/cargo clippy accept -A, -W, -D, -F flags (or the RUSTFLAGS environment variable, e.g. RUSTFLAGS="-D warnings" cargo build) that set a lint level for an entire build without touching any attribute in the source. This is the right tool for a CI-only, job-specific gate ("fail this build if there are any warnings") that shouldn't be baked into the source every downstream consumer also compiles with. This page covers the attribute form in depth, since that's the form that travels with the source and applies consistently regardless of how the crate is invoked.

The specific danger of denying the whole warnings group — writing #![deny(warnings)] instead of naming specific lints — is covered in depth on the Anti-pattern: #[deny(warnings)] concept page: it denies every lint a future compiler or Clippy release might add, not just the ones known today, which can turn a routine toolchain upgrade into a broken build. That page makes the full argument; this page is about the four-level mechanism itself.

Usage examples

Allowing and denying specific lints

#[allow(dead_code)] // <- silences the unused-code warning for this one function
fn planned_for_next_release() {}

#[deny(unused_must_use)] // <- turns this specific lint into a hard error, just for this function
fn must_check_the_result() -> Result<(), &'static str> {
    Ok(())
}

Designing a public API

A crate wants a small, curated set of lints to be hard errors — signaling "these are not negotiable" — without denying the open-ended warnings group and risking a future toolchain upgrade breaking the build on its own.

// AVOID: denies every lint any future compiler/Clippy release might add
// #![deny(warnings)]

// PREFER: names exactly the lints this crate treats as non-negotiable
#![deny(unused_must_use, unsafe_op_in_unsafe_fn)]
#![warn(missing_docs)] // <- tracked, but not yet a hard error

/// Parses a port number from a configuration string.
pub fn parse_port(input: &str) -> Result<u16, std::num::ParseIntError> {
    input.trim().parse()
}

Naming specific lints means a compiler or Clippy upgrade that introduces a brand-new warn-by-default lint doesn't retroactively turn previously-clean code into a build failure — see Anti-pattern: #[deny(warnings)] for the full argument against the blanket warnings group this contrasts with.

Implementing traits

A crate exposes an unsafe FFI wrapper module where a specific safety lint must never be silenced by accident, even by a contributor working several call-sites deep inside that module — forbid is what makes an inner #[allow] on that lint itself a compile error, rather than quietly succeeding.

#[forbid(unsafe_op_in_unsafe_fn)] // <- stronger than deny: can't be reopened by any nested scope
mod ffi {
    pub unsafe fn read_register(address: usize) -> u32 {
        // AVOID: this inner #[allow] would itself fail to compile under #[forbid] above
        // #[allow(unsafe_op_in_unsafe_fn)]
        unsafe { std::ptr::read_volatile(address as *const u32) }
    }
}

deny alone still allows a nested scope to locally allow the same lint back, which is appropriate for most lints but wrong for a safety-critical one a team wants to guarantee is enforced everywhere beneath a module boundary; the rustc book's lint levels chapter documents forbid as specifically closing that reopening loophole.

Explanation

Embedded support: Full

All four lint levels are pure compile-time configuration with zero runtime effect, so they apply identically in #![no_std] firmware — there's no embedded-specific mechanism here, just an embedded-specific reason #[allow(dead_code)] shows up so often: a chip-family HAL or a svd2rust-generated peripheral-access crate defines every register and field a chip family exposes, and any single application only ever touches a fraction of them, so #[allow(dead_code)] on generated register modules is standard rather than a sign of neglect.

Usage examples

Allowing unused fields across a generated register module

#[allow(dead_code)] // <- most fields here are unused by any single application; that's expected in generated PAC code
pub struct Gpiox {
    pub moder: u32,
    pub otyper: u32,
    pub ospeedr: u32,
    pub pupdr: u32,
}