21

I'm working on a rust project written a couple of years ago, and have come across this piece of code, which is literally:

let mut values = vec![];
for x in maybe_values {
    if let Some(x) = x {
        values.push(Arc::new(x));
    }
}

I understand that "if let" introduces a pattern-matching if (Which seems to be a poor re-use of the keyword "let", but I will get over that - If anyone can help me with a mental mnemonic to make sense of "let" here, please do!).

But what is the test Some(x) = x doing?

From my testing, it seems to be a trick/idiom to both a) test that the loop variant 'x' is Some(), and also b) end up with the unwrap()ped value in x.

But I can't fully explain it to myself, and can't find reference to this being an idiom anywhere.

Hope you can help my Rust education path. Thanks.

spechter
  • 2,058
  • 1
  • 17
  • 23
  • 3
    [How to "read" if let expressions?](https://stackoverflow.com/q/65684540/7884305); [Why is the let keyword in if-let?](https://stackoverflow.com/q/66516075/7884305) – Chayim Friedman May 02 '22 at 01:59
  • 2
    The `x` in `Some(x)` is a brand new variable, so I think the `let` is warranted. If anything, `let` is missing from pattern matching in a match statement: `match opt { Some(x) => {}, None => {} }` creates a variable `x` without a `let`, and this can be confusing if `x` is also the name of a `const` in scope. – BallpointBen May 02 '22 at 04:07
  • 1
    @BallpointBen Actually, by logic it should be `if Some(let x) = ...`, same in let statements and maybe even parameter declarations. And I know some people regret not having a variable matching modifier (like `let`) in patterns. – Chayim Friedman May 02 '22 at 05:26

3 Answers3

19

This is a shorthand for using a full match statement when you only care about matching a single use case.

So this block of code:

if let x = y {
   foo();
} else {
   bar();
}

Is equivalent to using a full match:

match y {
    x => {
        foo();
    }
    _ => {
        bar();
    }
}

For your specific case, it is equivalent to this. The inner x uses the same name as the outer variable which can be confusing, but they are two separate values.

let mut values = vec![];
for x in maybe_values {
    match x {
        Some(y) => values.push(Arc::new(y)),
        _ => {},
    }
}
Locke
  • 7,626
  • 2
  • 21
  • 41
  • 2
    If I could grant two green ticks I would ;-) I never thought of "if let" creating that new inner variable on the left hand side. That helps to make the use of "let" a bit clearer! TBH, when I started reading Rust code the "if let" statements were some of the strangest to me... Thanks. Personally I would try to avoid using the same variable name - "There is no team prize for tricky code!" Quite the contrary... – spechter May 02 '22 at 05:50
13

There are two completely different variables in play here. It's equivalent to.

let mut values = vec![];
for x_1 in maybe_values {
  if let Some(x_2) = x_1 {
    values.push(Arc::new(x_2));
  }
}

In Rust, the right-hand side of a let is evaluated with the left-hand variable not in scope, so when the if let is evaluated, the outer x is still in-scope. Then, if it's a Some value, we make a new variable x which contains the inside of the Option. This variable shadows the previous x, making it inaccessible inside the if statement (in the same way that a function argument called x would render a global variable named x inaccessible by shadowing).

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • Thanks! Not sure who replied first, but will give you the win for leading with the core answer - They are 2 separate variables, but both called x! – spechter May 02 '22 at 05:42
2

My 2 cents: Another good mnemonic to understand/remember if-let is as a shortcut to a very common pattern: if a.is_some() { let b = a.unwrap() ... } :

Full pattern:

let mut values = vec![];
for x_1 in maybe_values {
    if x_1.is_some() {
       let x_2 = x_1.unwrap()
       values.push(Arc::new(x_2));
    }
}

the shortcut if-let means joining the if x_1.is_some() and the let x_2 = x.unwrap() in a single if-let statement

With the shortcut:

let mut values = vec![];
for x_1 in maybe_values {
    if let Some(x_2) = x_1 {
       values.push(Arc::new(x_2));
    }
}
Lucio M. Tato
  • 5,639
  • 2
  • 31
  • 30