rust-lang / rust-lang/rust-clippy

Lint suggestion: Misleading usage of `?` with `let Some(...)`

Open
#16,140 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-lint
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.