When is `let _ = x;` equivalent to `x;`

So I know that the _ pattern (in)famously does not bind. As explained in this helpful comment, that means that let _ = x(); is equivalent to x();. We can see this in action:

#[derive(Debug)]
struct Foo;

impl Drop for Foo {
    fn drop(&mut self) {
        println!("Dropped!");
    }
}

fn main() {
    let _ = Foo; // prints "Dropped!"
    println!("Was it dropped yet?");

    Foo; // prints "Dropped!"
    println!("Was it dropped yet?");
}

If instead I bind Foo to a variable, I see that it gets dropped at the end of the scope which is expected:

fn main() {
    let foo = Foo;
    let _ = foo; // doesn't print "Dropped!"
    println!("Still in scope: {foo:?}");
    // prints "Dropped!"
}

By the "let _ = x(); is equivalent to x();" rule I expect the following code to behave the same but instead I get a compilation error:

fn main() {
    let foo = Foo;
    foo; // shouldn't print "Dropped!" // 👈 value moved here
    println!("Still in scope: {foo:?}"); // 👈 value borrowed here after move
    // should print "Dropped!"
}

It looks like let _ = foo; isn't just not binding to _, it's also not even moving foo into the let statement (so it's still alive afterward). But it seems like foo; is moving foo in and subsequently dropping it. Why is it that let _ = foo; doesn't move foo into the statement but foo; does?

For this part, the RHS is a place expression context. Just being on the right doesn't move foo. When you bind it by value, that's when it gets moved (or copied if it implements Copy). The wildcard pattern _ doesn't bind (at all, and not by value).[1]

This doesn't move foo either (it's not binding by value):

fn main() {
    let foo = Foo;
    let ref _r = foo;
    println!("Still in scope: {foo:?}");
}

An expression statement isn't a place expression context. (Why not? That I couldn't say.)


  1. The borrow checker doesn't even consider let _ = foo to be a use of foo. ↩ī¸Ž

3 Likes

To answer the title question, let _ = $expr; is equivalent to $expr; when $expr is a value expression and not a place expression. Temporary lifetime extension means the difference comes up less than it might otherwise. The traditional explanation of the difference is that a place expression can be used as the left hand side of an assignment, whereas a value expression can't. (Thus the terms lvalue and rvalue.)

A cute trick is that {$expr} is always a value expression, even if $expr is a place expression. ($expr) has the same expression kind as $expr.

7 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.