Why curly braces doesn't move variable here?


#1

Here is the code

use std::ops::Drop;

#[derive(Debug)]
struct D(i32);

impl Drop for D {
    fn drop(&mut self) {
        println!("destructor for {}", self.0);
    }
}
fn main() {
    let _x = D(1);
    println!("construct first variable");
    let _x = D(2);
    {_x;} // _x should destruct here
    // println!("{:?}", _x);
    println!("construct second variable");
    // but actually destruct here
}

I expected _x destructed in the middle curly brace {_x;}, but it gets destructed in the end of main function.
And change {_x;} to {_x};, it’s destructed as expected inside the curly brace.
Again I did more trial, change the curly brace to {_x; println!("{:?}", _x);}, _x again destructed inside the curly brace, even before the println! statement.

And I also noticed that {_x;} gives a warning path statement with no effect, what’s that meaning? Why these behavior differs?


#2

The lint tells you what’s going on. A path (in this case) is basically name of a variable, it’s a path because you could be referring to a static variable in another module. A path statement is a statement of the form e; where e is an expression that is only a path. It doesn’t mean anything, the compiler just completely ignores such statements. That is what the lint is telling you. And that is why it’s not moved.

Note that not all references of a variable move it. For example:

let a=Some("test".to_owned());
match a {
  Some(_) => println!("Some string!"),
  None => println!("No string!"),
}
// I can totally still use `a` here

If you want to explicity drop a variable, I suggest calling std::mem::drop on it.


#3

What about {_x; println!("{:?}", _x); }? the compiler says _x is moved.
If expr is an expression, then {expr} is also an expression, doesn’t a semicolon after an expression move that variable?


#4

Oh, huh, now I’m confused too :confused: I guess this means moving and destructing are not coupled the way you think. I just tested this on nightly with MIR and then the destruction does happen in the right order.


#5

Variables live to the end of the block they were created in, not just to end of block they were used in. You’ve created _x in main()'s block, so it will live to the end of main.

{_x;} doesn’t move the variable, because there is nothing to move it to. It’s more like reading the value and then doing nothing with it.


#6

See what i stated in my post

If a variable is moved to a block, then it will be destructed at the end of that block


#7

{x;} - borrow into statement (no new owner attached)
{x} - move into expression and return (block owns it and returns nowhere)


#8

But what about { _x; println!("{:?}", _x); }? The compiler still complains though no expressions involved.


#9

{_x} seems right as others have said, moved to return value of block.

{_x;}
warning: path statement with no effect
Possibly bug incorrect optimisation not evaluating the expression. (I’m not a rustc developer.)

{_x;_x;} is all that is needed to give error: use of moved value.
_x; _x; also.

https://doc.rust-lang.org/reference.html#path-expressions
lvalue mentioned here suggests to me move into the block (and subsequent end of block destruction) should be correct behaviour. (or destruction even sooner, thinking possibly same as let _ = _x; )

Can’t give a reason for wanting to see written {_x;} or {_x} in code over less confusing drop(_x);

let _x = D(2);
let _x3 = D(3);
{_x;}
println!("here");

vs

let _x = D(2);
let _x3 = D(3);
_x;
println!("here");

vs

let _x = D(2);
let _x3 = D(3);
let _x = _x;
println!("here");

Destruction order from three above seems to suggest that _x; should not move.


#10

There are two statements too, because both end with semicolon. Also, the second println macro expects borrowing in any case.


#11

Regarding the println! macro @tennix wrote above:
Again I did more trial, change the curly brace to {_x; println!("{:?}", _x);}, _x again destructed inside the curly brace, even before the println! statement.
Note before there. After would be reasonable, I assume.