9

I've seen a few other questions and answers stating that let _ = foo() destroys the result at the end of the statement rather than at scope exit, which is what let _a = foo() does.

I am unable to find any official description of this, nor any rationale for this syntax.

I'm interested in a few inter-twined things:

  • Is there even a mention of it in the official documentation?
  • What is the history behind this choice? Is it simply natural fall-out from Rust's binding / destructuring rules? Is it something inherited from another language? Or does it have some other origin?
  • Is there some use-case this syntax addresses that could not have been achieved using explicit scoping?
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187

2 Answers2

8

Is it simply natural fall-out from Rust's binding / destructuring rules?

Yes. You use _ to indicate that you don't care about a value in a pattern and that it should not be bound in the first place. If a value is never bound to a variable, there's nothing to hold on to the value, so it must be dropped.

All the Places Patterns Can Be Used:

  • match Arms
  • Conditional if let Expressions
  • while let Conditional Loops
  • for Loops
  • let Statements
  • Function Parameters

Is there even a mention of it in the official documentation?

Ignoring an Entire Value with _

Of note is that _ isn't a valid identifier, thus you can't use it as a name:

fn main() {
    let _ = 42;
    println!("{}", _);
}
error: expected expression, found reserved identifier `_`
 --> src/main.rs:3:20
  |
3 |     println!("{}", _);
  |                    ^ expected expression

achieved using explicit scoping

I suppose you could have gone this route and made expressions doing this just "hang around" until the scope was over, but I don't see any value to it:

let _ = vec![5];    
vec![5]; // Equivalent
// Gotta wait for the scope to end to clean these up, or call `drop` explicitly
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • I'm still not sure why it is natural fall-out from the binding / destructuring rules. Are there other `let pattern = fn()` type constructs, other than the pure `_`? (I'm not yet familiar with all rusts idioms - so this is an honest question.) – Michael Anderson Jul 09 '18 at 05:59
  • 3
    @MichaelAnderson yes, all `let` statements take patterns as the LHS. See [the reference](https://doc.rust-lang.org/reference/statements.html#let-statements). This allows tuple destructuring to work for example: `let (a, b) = (1, 2);` – Jmb Jul 09 '18 at 06:39
5

The only reason that you'd use let _ = foo() is when the function requires that you use its result, and you know that you don't need it. Otherwise, this:

let _ = foo();

is exactly the same as this:

foo();

For example, suppose foo has a signature like this:

fn foo() -> Result<String, ()>;

You will get a warning if you don't use the result, because Result has the #[must_use] attribute. Destructuring and ignoring the result immediately is a concise way of avoiding this warning in cases where you know it's ok, without introducing a new variable that lasts for the full scope.

If you didn't pattern match against the result then the value would be dropped as soon as the foo function returns. It seems reasonable that Rust would behave the same regardless of whether you explicitly said you don't want it or just didn't use it.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204