Changing lint configuration based on Cargo profile

I would like to enforce some strict linting rules when compiling with the release profile and a bit laxer configuration when compiling with the dev or test profiles. For example, the dev profiles should allow unused imports, unused variables and dead code.

  • All configurations enforce deny(warnings).
  • My project is going to grow into a workspace with multiple library crates.
  • All the crates within the workspace are going to share the linting configuration.
  • In addition to rustc lints, I am using a lot of deny Clippy lints, some of which need to be allowed for the dev profiles.

It looks like the cleanest way to start is to define the base linting configuration within the [workspace.lints] sections of my Cargo.toml.

What are my options to relax that base linting config for the dev and test profiles?

  1. Can Cargo selectively apply some [lints] only when compiling with a specific profile? Can I manually pass lint options to rustc based on the profile?

  2. There is the unstable profile-rustflags Cargo feature. Currently, the downside is that this won’t work with the stable toolchain until the feature is stabilized. Also, I’m not sure if it is possible to configure Clippy lints this way.

  3. Perhaps I could add something like #![cfg_attr(debug_assertions, allow(dead_code, unreachable_code, unused_imports, unused_variables))] to the root source file of each crate (all the main.rs and lib.rs files). Is there a simple way to avoid the code duplication and specify this allow list only once?

  4. Any other options?

1 Like

So I tried a few things with version 1.78.0-nightly (c475e2303 2024-02-28).

1. Conditional lint in Cargo.toml

I tried adding something like this to Cargo.toml:

[target.'cfg(debug_assertions)'.lints.rust]
dead_code = { level = "allow", priority = 2 }

It didn’t work. The allow rule was ignored and Cargo complained:

warning: Cargo.toml: Found debug_assertions in target.'cfg(...)'.dependencies. This value is not supported for selecting dependencies and will not work as expected. To learn more visit https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies

2. Conditional lint in .cargo/config.toml

On the other hand, trying to conditionally set rustflags in .cargo/config.toml causes Cargo to unconditionally use the flags for both dev and release builds.

[target.'cfg(debug_assertions)']
rustflags = ["-A", "dead-code"]

3. include! macro

I also tried including a snippet like this in the crate’s root source file (main.rs or lib.rs) using the include! macro. That didn’t work because apparently inner attributes like #![cfg_attr()] cannot be included (see issues rfc#752 and rust#66920).

#![cfg_attr(debug_assertions, allow(dead_code))]

Code generation?

So I’m out of options to eliminate the code duplication in the main.rs and lib.rs files. My actual list of allow rules contains more than just dead_code and keeps growing:

#![cfg_attr(
    debug_assertions,
    allow(
        dead_code,
        missing_docs,
        unreachable_code,
        unused_imports,
        unused_variables,
        clippy::missing_docs_in_private_items,
    )
)]

Keeping this list in sync across many crates is error-prone. One remaining solution would be setting up a build script to generate the main.rs and lib.rs files from templates before starting the build process.

If anyone has more ideas, please let me know!

Instead of denying all warnings in all profiles what about only denying them in CI by setting RUSTFLAGS="-Dwarnings" in the CI config. This is what a lot of projects do. This way you can still get warnings for things like dead code during development without either getting build errors or having to suppress them entirely and getting surprised once you test in CI.

1 Like