3

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.

E_net4
  • 27,810
  • 13
  • 101
  • 139
Martin Geisse
  • 1,189
  • 1
  • 9
  • 22
  • 1
    According to [the non-lexical lifetimes RFC](https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md#drop-as-last-use), your option 1 is true: the `Drop` trait creates an implicit use at the end of the block. – Jmb Oct 18 '22 at 07:14
  • Things don't get dropped after the last statement in which they're used. They're dropped at the end of the block, unless already manually dropped. – tadman Oct 18 '22 at 07:40
  • I believe this answer to be correct, according to the [official example](https://doc.rust-lang.org/reference/destructors.html). I mean Option 1. – xc wang Oct 18 '22 at 07:44
  • Interestingly, the NLL RFC sounds very much like option 2 to me: that RFC separates the "lifetime" of references from the "scope" of a value and defines NLL rules only for limetimes. On top of that, lifetimes are treated specially anyway because you can have lifetime parameters but not scope parameters. – Martin Geisse Oct 18 '22 at 08:39

3 Answers3

2

Do not look if a type implements Drop, look at the return value of needs_drop::<T>() instead.

That said, it is option (1): a type that needs_drop() has an implicit call to drop() at the end of the lexical scope. It is this call that extends the scope of the value.

So you code is as if:

fn main() {
    println!("A");
    let wrapper = create_wrapper();
    println!("B");
    println!("output: {}", wrapper.csp.data);
    println!("C");

    drop(wrapper); // <- implicitly called, wrapper scope ends here
}

Naturally, you can call drop(wrapper) anywhere to end the scope prematurely. As drop() takes its argument by value, it finishes the scope there.

If the type of a value does not needs_drop(), then it is released at the last usage of that value, that is a non lexical scope (NLL).

The non-lexical scopes affects not only references, but any type that doesn't need drop. The thing is that if a value doesn't need drop and doesn't borrow anything, then its scope does not have any visible effect and nobody cares.

For example, this code has a NLL that is technically not a reference:

use std::marker::PhantomData;

#[derive(Debug)]
struct Foo<'a> { _pd: PhantomData<&'a ()> }

impl<'a> Foo<'a> {
    fn new<T>(x: &'a mut T) -> Foo<'a> {
        Foo { _pd: PhantomData }
    }
}

fn main() {
    let mut x = 42;
    let f1 = Foo::new(&mut x);
    let f2 = Foo::new(&mut x);
    //dbg!(&f1); // uncomment this line and it will fail to compile
}
rodrigo
  • 94,151
  • 12
  • 143
  • 190
1

It's option two. Implementing Drop trait means that additional actions will happen when object is dropped. But everything will be at some point dropped, whether it implements Drop or not.

Aleksander Krauze
  • 3,115
  • 7
  • 18
  • I'd say it's more than option two, it's not just references that can end their scope earlier, it's any non-`Drop` type. This is due to these types not having any side effects on drop, so *when* they're dropped doesn't matter, so the drop can be moved to right after it's last usage. This is commonly referred to as NLL (non-lexical lifetimes). – Filipe Rodrigues Oct 18 '22 at 07:08
  • I agree that option 2 sounds very much like NLL and is probably what is happening, but your explanation actually confused me since it sounds like option 1... – Martin Geisse Oct 18 '22 at 08:40
0

println! is not taking your pointer playgroud...

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);

The thing here is that s is never taked, r1 and r2 are dropped after r3 because it takes a mutable reference to s playground

al3x
  • 589
  • 1
  • 4
  • 16