Description
Summary
Related: #14051
CC: @aaron-ang
It seems that return_and_then
does not take into account the surrounding context when suggesting a transformation.
The transformation suggested, changes meaning of the code (early return) and is only correct inside blocks returning an Option
. Anywhere else it produces code which does not compile, and if it did compile, it would behave differently.
I discovered this by running
cargo clippy --fix
So this fix is marked for autofix (or whatever it is called) but is not currently safe to use.
Really good lint I'd like to enable, but needs some more work.
I think 2 parts are missing here (the list is not exhaustive)
- check that we are about to return from the parent block (my code does not return; it's a
let
binding) - check that the parent block returns an
Option
(my block does not; it returnsResult
)
(I don't know the terminology, but by "parent block" I mean the block that the suggested ?
operator will return from)
Lint Name
return_and_then
Reproducer
I tried this code (a bit contrived, but derived from some real code):
#![warn(clippy::return_and_then)]
#![allow(dead_code, unused_variables)]
fn foo(_: &str, _: (u32, u32)) -> Result<(u32, u32), ()> {
Ok((1, 1))
}
fn bug(_: Option<&str>) -> Result<(), ()> {
let year: Option<&str> = None;
let month: Option<&str> = None;
let day: Option<&str> = None;
let _day = if let (Some(year), Some(month)) = (year, month) {
day.and_then(|day| foo(day, (1, 31)).ok())
} else {
None
};
Ok(())
}
fn main() {
println!("Hello, world!");
}
I expected to see this happen:
No warning, or warning with the suggested code being correct.
Instead, this happened:
warning: use the question mark operator instead of an `and_then` call
--> src/main.rs:14:5
|
14 | day.and_then(|day| foo(day, (1, 31)).ok())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#return_and_then
note: the lint level is defined here
--> src/main.rs:1:9
|
1 | #![warn(clippy::return_and_then)]
| ^^^^^^^^^^^^^^^^^^^^^^^
help: try
|
14 ~ let day = day?;
15 + foo(day, (1, 31)).ok()
|
After applying the transform, the code becomes:
#![warn(clippy::return_and_then)]
#![allow(dead_code, unused_variables)]
fn foo(_: &str, _: (u32, u32)) -> Result<(u32, u32), ()> {
Ok((1, 1))
}
fn bug(_: Option<&str>) -> Result<(), ()> {
let year: Option<&str> = None;
let month: Option<&str> = None;
let day: Option<&str> = None;
let _day = if let (Some(year), Some(month)) = (year, month) {
let day = day?;
foo(day, (1, 31)).ok()
} else {
None
};
Ok(())
}
fn main() {
println!("Hello, world!");
}
Not only it changes meaning (early return, which original code has no intention to do), but also triggering compiler error:
error[E0277]: the `?` operator can only be used on `Result`s, not `Option`s, in a function that returns `Result`
--> src/main.rs:14:18
|
8 | fn bug(_: Option<&str>) -> Result<(), ()> {
| ----------------------------------------- this function returns a `Result`
...
14 | let day = day?;
| ^ use `.ok_or(...)?` to provide an error compatible with `Result<(), ()>`
|
= help: the trait `FromResidual<Option<Infallible>>` is not implemented for `Result<(), ()>`
= help: the trait `FromResidual<Result<Infallible, E>>` is implemented for `Result<T, F>`
Version
rustc 1.86.0 (05f9846f8 2025-03-31)
binary: rustc
commit-hash: 05f9846f893b09a1be1fc8560e33fc3c815cfecb
commit-date: 2025-03-31
host: x86_64-unknown-linux-gnu
release: 1.86.0
LLVM version: 19.1.7
clippy 0.1.86 (05f9846f89 2025-03-31)
Additional Labels
No response