How to Configure Clippy Lints in Rust

Configure Clippy lints by adding rules to Cargo.toml or using attributes in your Rust source code.

When the compiler isn't enough

You're scanning a pull request. The logic works. The tests pass. But there's a Vec::new() followed immediately by reserve(). It's inefficient. You could comment it, but you've seen this pattern three times this week. You want the build to fail when someone writes it. That's where Clippy configuration comes in.

Clippy ships with hundreds of lints, but the defaults are conservative. They warn about obvious bugs and common mistakes. To enforce your team's style or catch subtle inefficiencies, you have to tune the lints. Configuration turns Clippy from a passive observer into an active guardian of your code quality.

Clippy as a code review bot

Clippy acts as a second layer of analysis on top of rustc. The compiler checks for type safety and memory validity. Clippy checks for idiomatic code, performance traps, and logic errors that are technically valid but practically wrong.

Think of Clippy as a code review bot that reads the entire codebase. Lints are the checklist items. Some items are red flags that stop the review. Others are yellow flags that suggest a better way. Configuration is where you customize the checklist for your project. You decide which rules are mandatory, which are suggestions, and which don't apply to your domain.

Lint levels and the Cargo.toml section

Every lint has a level. The level determines what happens when the lint triggers.

  • allow: The lint is silenced. No warning, no error.
  • warn: The lint prints a warning. Compilation continues.
  • deny: The lint turns into a compilation error. The build fails.
  • forbid: The lint is denied, and downstream code cannot override it with allow.

You set these levels in the [lints] section of Cargo.toml. This is the standard place for project-wide lint configuration.

[lints.clippy]
# Deny prevents compilation if this pattern is found.
approx_constant = "deny"

# Warn allows compilation but prints a message.
unused_self = "warn"

# Allow silences a lint that is too noisy for this project.
cast_precision_loss = "allow"

The [lints.clippy] table maps lint names to levels. The clippy key scopes these settings to Clippy. You can also configure rust lints in the same section using [lints.rust].

Run cargo clippy to apply these rules. The tool reads Cargo.toml, merges the settings, and runs the analysis. If a lint hits deny or forbid, the build stops.

Treat deny as your quality gate. If a pattern is bad enough to break the build, set it to deny.

Attributes for local control

Cargo.toml sets the baseline. Attributes in source code override the baseline for specific items. Attributes take precedence over Cargo.toml.

Use attributes when a lint is valid globally but wrong in a specific case. Or when you want to enforce a rule on a single function without affecting the rest of the crate.

/// This function intentionally uses a magic number for a hardware register.
#[allow(clippy::unreadable_literal)]
fn read_register() -> u32 {
    // The literal is obscure, but the hardware spec requires this exact value.
    0xDEADBEEF
}

/// This function must not be called with approximate constants.
#[deny(clippy::approx_constant)]
fn calculate_pi_area(radius: f64) -> f64 {
    // This will fail to compile if someone writes 3.1415 here.
    std::f64::consts::PI * radius * radius
}

Attributes apply to the item they decorate. #[allow(...)] on a function silences the lint inside that function. #[deny(...)] raises the level to error for that function, even if Cargo.toml says warn.

Convention aside: keep attributes close to the code they affect. Don't put #[allow(...)] at the crate root unless you have a compelling reason. Local overrides make it clear why the rule is broken.

Trust the attribute scope. If you silence a lint on a module, you silence it for every function in that module. That's often too broad.

Workspace inheritance and groups

In a workspace, you don't want to repeat lint configuration in every crate. Use [workspace.lints] in the root Cargo.toml to define shared rules.

# Root Cargo.toml
[workspace.lints.clippy]
# Enable the pedantic group with warnings.
pedantic = { level = "warn", priority = -1 }

# Override specific lints that are too strict.
too_many_lines = "allow"
single_match = "allow"

Crate-level Cargo.toml files inherit from the workspace. They can override individual lints.

# Crate Cargo.toml
[lints]
workspace = true

[lints.clippy]
# This crate is a CLI tool. Long lines are acceptable in the help text.
too_many_lines = "deny"

The workspace = true line pulls in the workspace configuration. The crate can then tweak specific lints.

Clippy provides lint groups to enable many lints at once.

  • pedantic: Lints that are generally good but might be too noisy for some projects.
  • nursery: New lints that are still experimental.
  • restriction: Lints that restrict valid code to enforce stricter standards.

Groups use a priority system. Lower priority numbers apply first. Higher priority numbers override lower ones. Setting priority = -1 on a group ensures that individual lint overrides in the same table take effect.

Convention aside: pedantic is the standard starting point for libraries. It catches style issues that reviewers usually flag. Enable it with warn, then allow the ones that annoy you.

Use workspace lints to enforce consistency. If every crate has different rules, the configuration becomes noise.

The clippy.toml configuration file

Cargo.toml handles lint levels. clippy.toml handles tool configuration. Some lints accept parameters. For example, too_many_arguments lets you set the threshold.

Create a clippy.toml file in the project root.

# clippy.toml
# Allow up to 12 arguments before the lint triggers.
too_many_arguments = 12

# Ignore specific files for the `module_name_repetitions` lint.
ignore-test-files = ["tests/integration.rs"]

Clippy reads clippy.toml automatically. The file supports workspace inheritance too. Put clippy.toml in the workspace root, and all crates use those settings.

Convention aside: keep clippy.toml small. Use it for thresholds and file ignores. Use Cargo.toml for lint levels. Mixing configuration types makes the setup harder to read.

Treat clippy.toml as the tuning dial. Adjust thresholds until the lints match your project's reality.

Pitfalls and compiler errors

Lint configuration has traps. The most common is the unknown lint error.

If you typo a lint name, Clippy rejects it with E0602 (unknown lint). The compiler doesn't know what you're trying to configure. Check the spelling. Clippy lints live in the clippy:: namespace. In Cargo.toml, you can omit the clippy:: prefix inside [lints.clippy]. In attributes, you must include it.

// This fails with E0602 because the prefix is missing.
#[allow(approx_constant)]
fn bad() {}

// This works.
#[allow(clippy::approx_constant)]
fn good() {}

Another trap is forbid. forbid prevents overrides. If you forbid a lint in a library crate, users of your crate cannot silence it. This can break downstream builds if the lint triggers in their code that calls your API.

Use forbid only for lints that must never be suppressed. Examples include lints that catch undefined behavior or security issues. For style lints, deny is usually enough. Users can allow a deny if they have a reason. They cannot allow a forbid.

Convention aside: libraries should avoid forbid on style lints. It forces your style on your users. That's a recipe for friction.

The allow trap is subtle. If you allow a lint everywhere, you lose the benefit of the lint. Use allow sparingly. Prefer expect for temporary suppressions.

#[expect(clippy::lint)] works like allow, but it warns if the lint is no longer triggered. This is useful when you suppress a lint for a known issue that might get fixed later. If the code changes and the lint stops firing, expect tells you that the suppression is dead weight.

Trust expect. It turns dead suppressions into warnings. You'll catch the moment your code improves.

Decision matrix

Use [lints] in Cargo.toml for project-wide lint levels. Set deny for patterns that break the build. Set warn for suggestions. Set allow only when the lint is genuinely wrong for your code.

Use [workspace.lints] in the root Cargo.toml to share rules across crates. Inherit with workspace = true in child crates. Override specific lints per crate when needed.

Use clippy.toml for tool configuration. Set thresholds for lints like too_many_arguments. Configure file ignores. Keep this file separate from lint levels.

Use attributes for local overrides. Apply #[allow(...)] to specific functions or blocks where the lint is a false positive. Apply #[deny(...)] to enforce stricter rules on critical code.

Use forbid for mandatory safety lints. Reserve this for cases where suppression would hide a bug or security risk. Avoid forbid for style preferences.

Use expect instead of allow for temporary suppressions. It warns you when the lint is no longer relevant, keeping your configuration clean.

Where to go next