Temporaries are dropped at the end of the statement, just like in C++. However, IIRC, the order of destruction in Rust is unspecified (we'll see the consequences of this below), though the current implementation seems to simply drop values in reverse order of construction.
There's a big difference between let _ = x;
and let _b = x;
. _
isn't an identifier in Rust: it's a wildcard pattern. Since this pattern doesn't find any variables, the final value is effectively dropped at the end of the statement.
On the other hand, _b
is an identifier, so the value is bound to a variable with that name, which extends its lifetime until the end of the function. However, the A
instance is still a temporary, so it will be dropped at the end of the statement (and I believe C++ would do the same). Since the end of the statement comes before the end of the function, the A
instance is dropped first, and the B
instance is dropped second.
To make this clearer, let's add another statement in main
:
fn main() {
let _ = B(&A as *const A);
println!("End of main.");
}
This produces the following output:
Drop B.
Drop A.
End of main.
So far so good. Now let's try with let _b
; the output is:
Drop A.
End of main.
Drop B.
As we can see, Drop B
is printed after End of main.
. This demonstrates that the B
instance is alive until the end of the function, explaining the different destruction order.
Now, let's see what happens if we modify B
to take a borrowed pointer with a lifetime instead of a raw pointer. Actually, let's go a step further and remove the Drop
implementations for a moment:
struct A;
struct B<'a>(&'a A);
fn main() {
let _ = B(&A);
}
This compiles fine. Behind the scenes, Rust assigns the same lifetime to both the A
instance and the B
instance (i.e. if we took a reference to the B
instance, its type would be &'a B<'a>
where both 'a
are the exact same lifetime). When two values have the same lifetime, then necessarily we need to drop one of them before the other, and as mentioned above, the order is unspecified. What happens if we add back the Drop
implementations?
struct A;
impl Drop for A { fn drop(&mut self) { println!("Drop A.") } }
struct B<'a>(&'a A);
impl<'a> Drop for B<'a> { fn drop(&mut self) { println!("Drop B.") } }
fn main() {
let _ = B(&A);
}
Now we're getting a compiler error:
error: borrowed value does not live long enough
--> <anon>:8:16
|
8 | let _ = B(&A);
| ^ does not live long enough
|
note: reference must be valid for the destruction scope surrounding statement at 8:4...
--> <anon>:8:5
|
8 | let _ = B(&A);
| ^^^^^^^^^^^^^^
note: ...but borrowed value is only valid for the statement at 8:4
--> <anon>:8:5
|
8 | let _ = B(&A);
| ^^^^^^^^^^^^^^
help: consider using a `let` binding to increase its lifetime
--> <anon>:8:5
|
8 | let _ = B(&A);
| ^^^^^^^^^^^^^^
Since both the A
instance and the B
instance have been assigned the same lifetime, Rust cannot reason about the destruction order of these objects. The error comes from the fact that Rust refuses to instantiate B<'a>
with the lifetime of the object itself when B<'a>
implements Drop
(this rule was added as the result of RFC 769 before Rust 1.0). If it was allowed, drop
would be able to access values that have already been dropped! However, if B<'a>
doesn't implement Drop
, then it's allowed, because we know that no code will try to access B
's fields when the struct is dropped.