struct Droppable;
impl Drop for Droppable {
fn drop(&mut self) {
println!("dropping");
}
}
struct S {
droppable: Droppable,
}
and two test functions:
fn test1() {
println!("test1");
let s = S { droppable: Droppable };
{
{
let S { droppable: _ } = s;
};
println!("should be dropped?");
}
println!("end of test1");
}
fn test2() {
println!("test2");
let s = S { droppable: Droppable };
{
{
let S { droppable: _droppable } = s;
};
println!("should be dropped?");
}
println!("end of test2");
}
The only difference between them is the destructuring of s: it's let S { droppable: _ } = s in the first case and let S { droppable: _droppable } = s in the second.
The second case behaves as I expect it to and droppable is dropped in the same block where s is destructured. I.e. i get
test2
dropping
should be dropped?
end of test2
But in the first case it's dropped at the end of the function.
This was very surprising and I actually had a bug because of it, because my real-world droppable needed to be dropped early.
But now I'm wondering why it works like this. I've read the chapter about destructors in the docs and couldn't find any explanation for this. Did I miss something?
I think it is due to the wildcard pattern being treated differently than identifier patterns when it comes to moving a value. If we use an identifier to assign the droppable value to a new variable, the value is moved to that variable and dropped when it goes out of scope. But if we use the wildcard pattern, the value isn't moved:
Unlike identifier patterns, it does not copy, move or borrow the value it matches.
But I thought that having *null in itself is already UB. Is there a rule that referring to invalid locations is fine as long as we don't read or write?
For another example of this see the &raw syntax. You can say &raw mut *null and it is completely fine because it just computes a new pointer without dereferencing it.
This blog post by one of the rust developers explains the concept really well in my opinion.
The analogy doesn't work: *null; is UB because it reads the value and discards it. In order to get away with *null you need to not read it, and that means you must use it in a place context that does something other than reading it. A bare expression as a statement is in value context, not place context.
Wow, this is more involved than I thought. So the right side of = would be a place expression if and only if the left side is an _? I don't see where it says that, though in the link, though.
Right, thanks! It does list "The initializer of a let statement" as a place context. It's surprising at first, because the right side seems to be the classical case of an rvalue, but I understand we need it to be a location so that we can take a reference to it.