Improvement to WIP #[expect(lint)] attribute

Relevant tracking issue

I had an idea to make the #[expect(lint)] attribute being worked on in that issue more useful, and the RFCs repo suggested I make a post here for feedback before writing an RFC, so I'm following that. Sorry if I do anything wrong, I'm new to contributing to Rust.

Quick summary of #[expect(lint)] attribute: It's like allow, but if no instances of the underlying lint exist in the region covered by this attribute, then it raises a warning (see the tracking issue for more details/discussion around edge cases).

For my code, this attribute would be useful to include allowances for lints of a limited scope with intention to remove them eventually from the code, and expect producing a warning is a great signal to the programmer that the allowance is no longer needed and can be removed. Obviously, it is preferable to limit the scope of the allowance to only the offending code, so no such addition is possible, but this is only sometimes possible (e.g. no way to put an allow on the body of a derive impl).

However, this approach has a drawback: new code can be written in the same region as existing exceptions that adds new exceptions to the lint, and the allowance means this won't be discovered.

To ameliorate this drawback, I propose extending the attribute by allowing #[expect(lint, count = N)] (count = N being optional), which will raise a warning if there aren't exactly N instances of the offending lint in the region covered by the expect attribute, and it will only suppress the internal lints if there are exactly the right amount (I'm flexible on this last part, but imo it'll be easier to use if you see all != N lints so you know what changed).

For an example, to slightly modify the original RFC that proposed the #[expect(lint)] attribute:

#[expect(unused_mut, count = 1)]
fn no_lints() -> usize {
    let mut a = Vec::new();
#[expect(unused_mut, count = 1)]
fn lints() -> usize {
    let mut a = Vec::new();
    // New code was added that also triggers `unused_mut`
    let mut b = Vec::new();

The no_lints function would compile without any lints, since there is one instance of unused_mut that would have triggered without the expect being in place. On the other hand, lints would produce three lints: one for each of the unused_mut triggers(a and b), and a lint on the #[expect(...)] attribute indicating that there were 2 instances, not 1.

Any thoughts on this?


It can also exist on allow, removing the need for a new expect attribute.

I don't think this would fully supplant the need for expect, since you can't reproduce the default behavior of no provided count (sorry if it was unclear, I'm proposing the count be optional).

Without the count part, this could be a new lint unnecessary_allow or similar. That’s less flexible, but has the advantage of not requiring any new attributes or new “shapes” for existing attributes.

Yeah, I think the lack of flexibility with your approach is why the tracking issue I linked is going the way that it is instead of introducing that lint. Lints are specified by an attribute on the section of code you want to apply the lint to, and as such there's no way to specify your unnecessary_allow to a specific #[allow(..)] attribute.