Idea: #[must_use = inherit]

An ability to inherit reason of #[must_use] (not whether it is #[must_use]). If there is no #[must_use] to inherit, it errors.

Motivation

Side note: I know there are other cases that #[must_use] gets triggered without putting #[must_use] on the function, but will mention only return types for briefness.

There is a clippy lint double_must_use, warn by default. The lint warns:

#[must_use] // warning!
fn f() -> Result<Thing, ()> {
    todo!()
}

The point of the above is:

  1. Result<T, E> is already marked as #[must_use] with some message.
  2. f is marked as #[must_use], with no message.
  3. 1 and 2 are reported as separate diagnostic and #[must_use] on f is less informative, therefore #[must_use] on f should be removed.

It seems reasonable, but my concern is:

// Someday `Result<T, E>` became no longer needed, so got back to just `T`
// But I forgot to put `#[must_use]` again!
fn f() -> Thing {
    todo!()
}

I found that what we want is "propagate the #[must_use] message", not "let's delegate whether the function is #[must_use], to the return type".

Does removing #[must_use] from fn f() -> Result<Thing, ()> mean the return value no longer must be used? No. It still must be used, but messages are taken from the return type. Explicit #[must_use = inherit] reflects this semantic well.

Additionally Needed Change

  1. 1 and 2 are reported as separate diagnostic and ...

We need to change this behavior to emit single warning, before introducing this feature. If we leave it as it is, the output will be quite confusing as warnings will get doubled. I'm considering to make #[must_use] on a function to override #[must_use] on the return type as it seems rational behavior, but it is problematic because it removes relatively more informative messages, not just confusing users a bit. Maybe we could make #[must_use = inherit] the default behavior for #[must_use], but I'm not sure if it is a breaking change.

1 Like
#[must_use]
#[expect(clippy::double_must_use)]

should warn when the lint stops applying, I believe

I don't think that solves the OP's problem – the issue is that adding a plain #[must_use] overrides/hides any message that would otherwise be produced by a #[must_use] on its return type, so any solution would have to either remove the #[must_use] from the function or else cause the message to not be shadowed.

It does work, but still has two downsides:

  • Verbose. I should put #[expect(clippy::double_must_use)] on every function that returns a Result<T, E>(for example). In addition, if you turned on allow_attributes_without_reason(I did!), now it becomes #[expect(clippy::double_must_use, reason = "...")]. That will be nightmare IMO.
  • Doubled warning, what double_must_use is concerned about.

To be clear, As of now, #[must_use] does not override/hide messages produced by return type, but inserts additional warning: Playground