I'll use the CustomSmartPointer from The Book, which is used to explain the Drop trait, to build an example:
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
println!("A");
let pointer = CustomSmartPointer {
data: String::from("my stuff"),
};
println!("B");
println!("output: {}", pointer.data);
println!("C");
}
This prints:
A
B
output: my stuff
C
Dropping CustomSmartPointer with data `my stuff`!
However, from what I learned I would have expected the last two lines to be swapped. The local variable pointer isn't used anymore after being printed, so I would have expected its scope to end after the line that prints its contents, before "C" gets printed. For example, the section on references has this example:
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// variables r1 and r2 will not be used after this point
let r3 = &mut s; // no problem
println!("{}", r3);
So it seems like either (1) the Drop trait extends the scope until the end of the current block, or (2) it is only references whose scope ends "ASAP" and everything else already lives until the end of the current block (and that would be a major difference between references and smart pointers), or (3) something entirely else is going on here. Which one is it?
Edit: another example
I realized one thing while reading the answers so far: that (1) and (2) are equivalent if (1) includes cases where a type does not implement the Drop trait but "things happen while dropping", which is the case e.g. for a struct that contains another value that implements Drop. I tried this and it is indeed the case (using the CustomSmartPointer from above):
struct Wrapper {
csp: CustomSmartPointer,
}
fn create_wrapper() -> Wrapper {
let pointer = CustomSmartPointer {
data: String::from("my stuff"),
};
Wrapper {
csp: pointer,
}
}
fn main() {
println!("A");
let wrapper = create_wrapper();
println!("B");
println!("output: {}", wrapper.csp.data);
println!("C");
}
This still prints "Dropping CSP" last, after "C", so even a non-Drop wrapper struct that contains a value with the Drop trait has lexical scope. As hinted above, you could then equivalently say: the Drop-able value inside the struct causes a usage at the end of the block that causes the whole wrapper to be dropped only at the end of the block, or you could say that only references have NLL. The difference between the two statements is only about when a value gets dropped that is deeply free of Drop-trait values, which isn't observable.