Don't allow, expect! (and be reasonable)

Just a quick PSA that might be helpful, especially to new Rust users—though I've mentioned this to quite a few experienced Rustaceans who weren't aware of it either.

When your code gets a lint warning, either from Clippy or from the compiler, the right thing to do is fix the code, of course—Clippy is always right. But let's say there's some compelling reason why you want to suppress this particular lint, in this particular place, at this particular time. You reach for allow, right?

#[allow(non_camel_case_types)]

But the trouble with this is that when the code eventually changes so that it would no longer trigger the lint, you don't remove the now-unnecessary allow because you're not seeing the lint, so you're not thinking about it. Result: your code gets cluttered up with a million obsolete allow directives—and the danger is that you might end up accidentally suppressing a lint you needed to see.

Instead, use expect:

#[expect(non_camel_case_types)]

expect suppresses the lint as long as the code it's attached to triggers it, but warns if it doesn't! In other words, once the need for the expect goes away, you'll be reminded to remove it. Very handy. I can't think of any reason not to use expect instead of allow everywhere (if you can, let me know). The Clippy allow_attributes lint will prompt you to upgrade allow to expect.

Speaking of reasons, I think it's a great idea to use the reason parameter to explain why it's okay to suppress this lint (for now):

#[expect(non_camel_case_types, reason = "I just like them, okay?")]

This way, you can surface your assumptions about why this lint should be suppressed. Those assumptions may be wrong, either now or later, and an explicit reason makes this kind of thing easier to spot.

The Clippy allow_attributes_without_reason lint will nudge you to add reasons (to expect as well as allow).

22 Likes

What if you expect your code to be built with different versions of Rust/Clippy, that may or may not trigger the lint?

2 Likes

I think there are some cases where allow does make sense. For example:

#[allow(unused_unsafe, reason = "Safe since Rust 1.92.0")]
unsafe {
    core::ptr::addr_of!(self.repr.bitmap)
}

Accessing the field of a union is only unsafe on some versions, so using expect would fail to compile on old compilers.

8 Likes

In modules with complex combinations of features, I write

#![cfg_attr(not(feature = "some_feature"), allow(unused_imports))]

This way, I don't have to individually annotate each use with when it is applicable and keep those up to date, but I still get warnings of completely unused imports when building with --all-features.

Other than that and the other cases mentioned, I do use expect most places — especially on false positives (which I report to the issue trackers) because I expect those to go away on their own and I want to clean up the notes when they do.

2 Likes

What if you expect your code to be built with different versions of Rust/Clippy, that may or may not trigger the lint?

Good point! I suppose when you update your MSRV, you could temporarily change all allows to expects, and see if any of them can now be removed.

Accessing the field of a union is only unsafe on some versions, so using expect would fail to compile on old compilers.

Thanks, @alice! I knew someone would have a good idea about this.