---
tags: Rust, rustc
---
# Lint expectations in Rust: Attribute name and interaction with other lint levels
## Background
[RFC 2383] proposed the addition of lint expectations in the form of an attribute. The RFC named this attribute `#[expect]` and described the functionality as follows:
> This RFC adds an `expect` lint attribute that functions identically to `allow`, but will cause a lint to be emitted when the code it decorates **does not** raise a lint warning.
The RFC included two examples:
* Example 1
```rust
#[expect(unused_mut, reason = "Everything is mut until I get this figured out")]
fn foo() -> usize {
let mut a = Vec::new();
a.len()
}
```
The expectation is fulfilled and nothing is emitted.
* Example 2
```rust
#[expect(unused_mut, reason = "...")]
fn foo() {
let a = Vec::new();
a.len()
}
```
which should emit a warning like this:
```
warning: expected lint `unused_mut` did not appear
reason: Everything is mut until I get this figured out
--> src/lib.rs:1:1
|
1 | #[expect(unused_mut, reason = "...")]
| -------^^^^^^^^^^-----------------
| |
| help: remove this `#[expect(...)]`
|
= note: #[warn(expectation_missing)] on by default
```
A working implementation of the `#[expect]` attribute can be found on the [Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=dd6dd677c432f4a3ceea4c0c22b7e09c)
See:
* [RFC 2383]
* [rust#54503]
[RFC 2383]: https://rust-lang.github.io/rfcs/2383-lint-reasons.html
[rust#54503]: https://github.com/rust-lang/rust/issues/54503
## Questions / Discussion
### Q1: What should the attribute for lint expectations be called?
The RFC suggested the attribute name `#[expect]`. In the comments, some concerns were expressed, that [the name might be too generic](https://github.com/rust-lang/rust/issues/54503#issuecomment-1179570739) and/or should be reserved for other features. [`#[should_lint]`](https://github.com/rust-lang/rfcs/pull/2383#issuecomment-378648877) or `[expect_lint]` were proposed as an alternative names.
**Options**
* `#[expect]`
* ++ Short name
* ++ Similar to other lint level attributes like `#[allow]`, `#[warn]`, `#[deny]`
* -- Might be too generic
* `#[should_lint]` | `[expect_lint]`
* ++ Not generic and has a clear purpose
* -- Verbose and different from other lint level attributes
---
<!-- Comments and Answers -->
### Q2: How should lint attributes inside the expectation region be handled?
The RFC states that a lint expectation should suppress lint emissions inside the marked scope and emit a warning if nothing was emitted. This leads to the question: How should nested lint attributes be handled?
Example q2.1 ([Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=1c7f447f0cab0ac00ac756e0cbe2c829)):
```rust
#![feature(lint_reasons)]
#[expect(unused_variables)]
pub fn f() {
#[warn(unused_variables)]
let x = 1;
}
```
**Options**:
1. The lint level is changed by nested attributes, and the expectation won't be fulfilled by the lint emission.
For example q2.1: The `unused_variables` lint would be emitted for `x` since the lint level was changed to `warn`. The `unused_variables` expectation would not be fulfilled. (This is how the current implementation works.)
* ++ The `#[expect]` attribute can be overridden, like other lint levels can
* ++ In the example, I'd say that the intention of the programmer is clear, that the lint `unused_variables` should be emitted for `x`
* ++ All `#[allow]` attributes can be replaced with `#[expect]`, without changing how lints are emitted
* ++ An `#[expect]` attribute can be removed in steps, adding `#[warn]` to inner scopes until no lint is suppressed anymore
* -- It could be confusing, why the lint emission is not suppressed and the expectation is not fulfilled
2. The expectation suppresses all lint emissions
For example q2.1: The `unused_variables` lint would be suppressed and the expectation would be fulfilled
* ++ The expectation surpasses all lint emissions in the marked scope
* -- It could be confusing, why the lint is not emitted
* -- `#[expect]` would behave differently than `#[allow]` and other attributes
3. Deny nesting other lint attributes inside `#[expect]`, similar to `#[forbid]`
For example q2.1: The code would emit an error, similar to how `#[forbid]` can't be overridden.
* ++ The behavior can be changed later to option 1. or 2.
* -- Replacing `#[allow]` with `#[expect]` could cause errors
* -- I expect this to negatively effect the adoption of the `#[expect]` attribute
* -- Some macros include `#[allow]` attributes, to prevent lint triggers in generated code. Forbidding nested lint attributes could cause unexpected errors.
---
<!-- Comments and Answers -->
### Q3: Should the outer lint level effect lint expectations?
The RFC states that the lint expectation should be fulfilled by suppressing otherwise emitted lints. This leads to the question: Should the expectation be fulfilled if the lint was allowed before?
Example q3.1 ([Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=a47f349b73b677bde7f0c34cf86658a8)):
```rust
#![feature(lint_reasons)]
#![allow(unused_unsafe)]
#[allow(unsafe_code)]
fn main() {
#[expect(unsafe_code)]
unsafe {
}
}
```
**Options**
1. The `#[expect]` attributes suppresses all lint emissions that would be triggered for a `#[warn]` attribute.
For example q3.1: The lint expectation would be fulfilled, since `#[warn]` instead of `#[expect]` would have issued a warning.
* ++ Lint expectations are independent of outer lint levels. This is especially important for lints which are usually allowed but are only enabled before releases or in the CI via console arguments
* ++ Similar to other lint attributes
* ++ This is how the current implementation works
* -- Users might be confused, why an expectation is fulfilled, but no warning is emitted when the attribute is removed.
2. The `#[expect]` attribute only suppresses lints if they have the warn level or higher at the attribute.
For example q3.1: The lint expectation would be unfulfilled as the lint is already allowed and no warning is emitted before the expectation was added
* ++ Removing the expectation would always result in a warning, as users might expect
* -- Adding `#![allow]` to the project module could have a cascading effect on existing `#[expect]` attributes
* -- Lint expectations would be effect by lint levels set with console arguments
---
<!-- Comments and Answers -->
## Questions from the lang meeting
### Pondering the mental model
scottmcm: Looking at a bunch of the ±, I get the feeling that there's an implied "is this a new level or a different feature?" overall question. How should people think about this? Would there ever be an `-E warnings` like there's a `-D warnings`?
xFrednet: Yes, I feel like this was one of the main points of discussion. I feel like it's a different level, but `-E warnings` won't be a thing.
joshtriplett: I think we might want to consider disallowing `expect(warnings)`, since it's so non-specific. Which warning are you expecting? I have a hard time imagining it proving useful.
### Perf implications
scottmcm: Some of the options seem to imply that you could expect something that's allowed. That seems like it'd require running those lints when we wouldn't otherwise have to. Is that a perf concern? Or could it be scoped just to where there's `expect`s in play so it's an opt-in perf hit?
joshtriplett: I'd expect it to have *exactly* the peformance implications of `warn`. (If we can optimize `warn` on a scoped basis, we can optimize `expect` the same way)
xFrednet: It's implemented like a warn, we use `expect` in clippy (also as a test project) and haven't noticed any negative effects related to performance.
### Addressing Confusion
pnkfelix: I too am confused by the expect/warn example. :) Maybe that is in part because I'm not sure whether there are two distinct cases that should be handled separately: an expect where you indeed expect a warning and do not want it silenced, vs an expect where you want to also allow it (i.e. silence the warning)?
pnkfelix: maybe in other words: How else should I write down my expectation *without* silencing the warning? And how should I write down an epectation in that expect+warn case where my intent is to signal that I *expect* that warning to fire?
xFrednet: One question was how to handle `force_warn`. Currently it fulfills the expectation but still emits the warning. Could be useful at a tool level.
josh: If we were redesigning Rust I might use `allow` for what `expect` does here, and use a longer attribute for "allow this and I don't care if it actually happens".
scottmcm: The one exception to that might be macros, where you may need to allow something that might or might not happen.
### Current lint level vs "bubbling out"
tmandry: Is it workable to think of this as lints "bubbling out" of the scope if they aren't caught by `allow`/`expect` to higher levels? `forbid` would be a different category that restricts what you can do in lower level.
```rust
#![feature(lint_reasons)]
#[expect(unused_variables)]
pub fn f() {
#[warn(unused_variables)]
let x = 1;
}
```
tmandry: So in this model the warn on `x` would fire, but the "unused expectation" would not.
xFrednet: I see it as more of a stack; in this scope this is the level.
tmandry: I'm wondering if that is more of an implementation-focused model, whether it's intuitive for users.
pnkfelix: Similar reaction
Josh: I would expect to get an unused_variable lint and _not_ get an unfulfilled expectation lint.
xFrednet: Example: Let's say you had an expect at the top of a file, and then want to incrementally replace the expect with warn.
Josh: So you expect the expect to only "work" if it is actively suppressing a lint. Seems like a valid intuition.
xFrednet: What's the use case?
josh: I'd expect that the only purpose of adding a warn would be to promptly fix the warn.
xFrednet: Same thing can be achieved without that. Might be that you get a warning that the expectation is no longer fulfilled, you get two warnings instead of one.
Josh: Different intuitions, both seem valid, we need to figure out which one we want.
Bryan Garza: I think of it as colors applied to a scope. Wouldn't think of the warn as going back up in any way, just changes what happens going further in.
xFrednet: Also depends on how you phrase it. I think of it as "it suppresses a warning" but you could also phrase it as "if a warning is emitted" in the scope.
Mark:
```rust
#[expect(unsafe_code)]
pub fn f() {
unsafe {
}
}
```
In this example from the RFC, `unsafe_code` is not emitted by default
xFrednet: Revision: "if it would be emitted if you had replaced `expect` with `warn`".
```rust
#[warn(unsafe_code)]
pub fn f() {
unsafe {
}
}
```
Josh: Imagine you have an `expect` with a nested `allow` in a macro. You don't want the `allow` in the macro to fulfill your expectation. It's pretty clear that `allow` should not cause an expect to be fulfilled; maybe for consistency's sake we should do the same for `warn`.
Mark: Not sure I buy the argument that that's consistency, though. unused_variables is warn-by-default. I would expect that if you add `warn` to a lint that's already warn-by-default it would be a no-op. Not sure if that's self-consistent.
pnkfelix: Coupling expect with allow the right thing?
## Q1: Name
Josh: Should look like the other ones; does anyone object to `expect`?
scottmcm: To me it's a model question. If it's a lint level that behaves like others, it should be expect, otherwise (with e.g. Tyler's model) something like `should_lint` leans into that.
pnkfelix:
xFrednet: `lint`?
xFrednet: Different use case for `expect`?
pnkfelix: Contracts maybe.
tmandry: +1 to contracts, but there are other names they could potentially use. precondition, postcondition, require(scottmcm)
tmandry: Agree that if it's more of a lint level then `expect` makes sense.
## Q3: From the outer level
Mark: A lot of this discussion is around contrived models, theoretical discussion. Can we hunt down actual usages and not worry about edge cases?
xFrednet: I went through Clippy codebase in https://github.com/rust-lang/rust-clippy/pull/8797 and saw if we could replace allows with expect. Only thing we noticed was that some allows were unneeded and could be removed, everything else was smooth.
Josh: Now I almost want a `warn(allow)`. Warn me if I ever use allow in my code instead of `expect`.
tmandry: I agree with looking at practical examples, slightly dubious about putting too much weight on a single one. (Though clippy is not a bad one)
Josh/pnkfelix: Seems unlikely we'll get one soon, it's unstable. Good idea to try it out in the compiler.
scottmcm?: Do we expect difference in use case between rustc lints and clippy lints?
Josh: Another use case: expect naming lints on a module with a bunch of C types.
scottmcm: Reason for expect vs allow?
Mark: Might be worth finding answers to questions around: CLI flags; are there cases where some subset of code under that is changing the level? If we can't find examples, maybe forbid it.
xFrednet: In the latter case you have macros, wouldn't necessarily forbid it.
xFrednet: Think of command line as not part of your code