If let Some vs for in

I find the following syntaxes equivalent:

if let Some (without else):

if let Some(x) = option {
  ...
}

for in:

for x in option {
  ...
}

Questions

  1. Is there any subtle difference?
  2. Which one compiles faster?
  3. Do they produce the same binary code?
  4. What do you personally prefer?
2 Likes

There should be no real difference, since IntoIterator implementation for Option in the end just calls take, which delegates to mem::take. However, if let should be in most cases more clear for future readers.

4 Likes

The only difference is that if let is much more readable.

3 Likes

There's a Clippy lint that answers your question: for_loops_over_fallibles

for_loops_over_fallibles [Correctness] [Deny]

  • What it does

Checks for for loops over Option or Result values.

  • Why is this bad

Readability. This is more clearly expressed as an if let .

  • Known problems

None.

  • Example

// Bad
for x in opt {
    // ..
}

// Good
if let Some(x) = opt {
    // ..
}

// or

// Bad
for x in &res {
    // ..
}

// Good
if let Ok(x) = res {
    // ..
}
1 Like

As a slight aside, why is this tagged “Correctness” instead of “Style”? The flagged code operates correctly.

@2e71828 That's a good question. I'm not sure either, but here's my guess:

Looking at the list of all "Correctness" lints, it seems that these lints flag code that probably don't work the way the author intends. For this one, it is possible that one erroneously iterates over an iterator wrapped in an Option and intends to iterate over the contained iterator instead. For example: (contrived)

let names = /* Option<Vec<String>> */;
for name in names {
    println!("{}", name);
}

I would like someone to verify my hypothesis :slight_smile:

9 Likes

Yes, that's correct. If you used a for loop, you probably thought that it was a list of some kind.

3 Likes

Second reason: for <pattern> in opt is an expression that consumes / takes ownership of opt, no matter the <pattern> used, whereas if let Some(<pattern>) = opt will only consume opt if the <pattern> does:

let mut opt;

opt = Some(42);
if let Some(ref mut x) = opt {
    *x += 27;
}
assert_eq!(opt, Some(42 + 27)); // Passes

opt = Some(42);
for ref mut x in opt {
    *x += 27;
}
assert_eq!(opt, Some(42 + 27)); // Fails
6 Likes

for x in option looks like a bug to me. I know it technically works, but whenever I see such code I doubt whether the author actually meant to make a loop out of a single element, and suspect the code accidentally failed to unwrap somewhere.

As for "which generates better code", you can check at https://rust.godbolt.org/ (but don't forget to add -O to flags, because godbolt defaults to debug mode, which always generates awful code that is not representative of performant use of rust)

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.