I was playing around recently with the new let chains feature and read several old problems about let chains on GitHub. From this issue I found this code. Which generates the following compilation error:
|
11 | if let n = ()
| - binding `n` declared here
12 | && let _ = B(&n)
| --^^-
| | |
| | borrowed value does not live long enough
| a temporary with access to the borrow is created here ...
13 | {};
| -- ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `B`
| |
| `n` dropped here while still borrowed
Meanwhile I think I understand the drop order stuff about let chains and also the error in 2021 edition but what I currently don't understand is why the RHS of let _ = B(&n) results in a temporary in the first place according to the compiler error? I would understand when it creates a temporary for let _ = &B(&n) but not for let _ = B(&n).
I don't know why B(&n) is referred to as a temporary in this context, but the destructor on B is problematic, because dropck doesn't understand what a destructor does. By default no reference stored in a type is allowed to dangle at the time the destructor of an instance of said type is executed. You can use the dropck eyepatch to pinky-swear to the compiler that you won't access a potentially dangling reference in the destructor:
#![feature(let_chains)]
#![feature(dropck_eyepatch)]
#![allow(irrefutable_let_patterns)]
struct B<'a>(&'a ());
unsafe impl<#[may_dangle] 'a> std::ops::Drop for B<'a> {
fn drop(&mut self) {}
}
fn f() {
if let n = ()
&& let _ = B(&n)
{};
}
I don't know why B(&n) is referred to as a temporary in the error either (even if you bind it to a variable). Perhaps it's some drop elaboration issue? But I'm not sure how much time I want to pour into exploring that either, unless something analogous can be demonstrated on stable/edition 2024.
However, as to the titular question -- why is a temporary created?
Well, what is a temporary? It's a value not associated with a variable or other pre-existing place (like a derefence of a Box<_>). They have drop scopes, similar to how variables do. Otherwise their destructors would not get ran.
So these create temporaries:
String::from("hi");
// ^^^^^^^^^^^^^^^^^^ a temporary
// Same thing since `_` doesn't bind
let _ = String::from("hi");
// ^^^^^^^^^^^^^^^^^^ a temporary
The String is deallocated when the temporary drops.
And actually this does too:
let x = String::from("hi");
// ^^^^^^^^^^^^^^^^^^ a temporary
The temporary just gets immediately moved into x instead.
And similarly with B(&n) (though again I can't give a satisfactory explanation for that exact error).
Thanks for your answers! Okay, that's reassuring, because I was starting to think I might have misunderstood something fundamental here. Perhaps the problem is simply that the compiler's explanation is unfortunate or something...