rust-lang / rust-lang/rust-clippy
Lint suggestion: Misleading usage of `?` with `let Some(...)`
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 13.5k
- Forks
- 2.2k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 32
Description
What it does
I've seen Rust newcomers bitten by ? semantics with Option when they're explicitly using let Some(...). This is a footgun that's discussed at length on this Rust internals thread. As languages like TypeScript grow in popularity, I think it will be easier to make this mistake.
fn display_name(nicknames: Option<Vec<String>>, use_placeholder: bool) -> Option<String> {
if use_placeholder {
// This looks totally innocent to a Rust newbie. If they're expecting
// TypeScript semantics then `?.` does not look like an early return.
if let Some(first_nick) = nicknames?.first() {
return Some(first_nick.to_owned());
}
return Some("Anonymous".to_owned());
}
nicknames.unwrap_or_default().first().cloned()
}
fn main() {
let use_placeholder = true;
// The programmer expected Some("Anonymous"), but got None.
println!("Display name: {:?}", display_name(None, use_placeholder));
}
I would love to have a lint that warns on let Some(...) = foo?, if let Some(...) = foo? or if let Some(...) = foo().bar? when ? is used on an Option. Mixing let Some(...) and ? can be misleading.
Advantage
Mixing let Some(...) and ? inside a function returning Option can make the early return less obvious. Avoiding ? in this case would make the code more explicit.
Drawbacks
This potentially annoys experienced users who are accustomed to ? semantics, but I think this combination is rare in practice. Grepping some open source code for let Some\(.*=.*\? looks like the majority are for ? with Result.
Another alternative would be banning ? entirely for Option in codebases with newbie contributors. That feels more extreme and I don't see any clippy lint that offers it today.
Example
fn display_name(nicknames: Option<Vec<String>>, use_placeholder: bool) -> Option<String> {
if use_placeholder {
if let Some(first_nick) = nicknames?.first() {
return Some(first_nick.to_owned());
}
return Some("Anonymous".to_owned());
}
nicknames.unwrap_or_default().first().cloned()
}
Could be written as:
fn display_name(nicknames: Option<Vec<String>>, use_placeholder: bool) -> Option<String> {
if use_placeholder {
// Alternatively, use .and_then() on the option.
if let Some(nicknames) = nicknames {
if let Some(first_nick) = nicknames.first() {
return Some(first_nick.to_owned());
}
}
return Some("Anonymous".to_owned());
}
nicknames.unwrap_or_default().first().cloned()
}
Comparison with existing lints
There's no pre-existing lint for this case as far as I can see.
Additional Context
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The issue names no implementation files or tests. Start by locating Clippy's lint implementations and tests for Option, ?, and if let patterns, then determine how the proposed cases should be distinguished from Result usage. Done means the misleading Option patterns are detected without incorrectly warning on the supported Result cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100