Search code examples
rustreferencedrop

Why Drop trait is only executed at the end of the scope, instead of after the last use?


This is a question from rust onomicon # lifetime

The first example can compile, as x is a reference and the compiler can infer its lifetime as minimal as the last use here :println!(), so x is dropped after this line.

let mut data = vec![1, 2, 3];
let x = &data[0];
println!("{}", x);
// This is OK, x is no longer needed
data.push(4);

But the case is different when x is a struct implemented Drop trait.

#[derive(Debug)]
struct X<'a>(&'a i32);

impl Drop for X<'_> {
    fn drop(&mut self) {}
}

let mut data = vec![1, 2, 3];
let x = X(&data[0]);
println!("{:?}", x);
data.push(4);
// Here, the destructor is run and therefore this'll fail to compile.

The onomicon says in this case, drop() is only executed at the very end of a scope, so x keeps valid until the last line.

But why the compiler cannot minimize the lifetime of x to the last use? And is applying drop() just after the last use has some nontrivial side effects when x is implemented Drop trait?


Solution

  • The primary reason is that it was once defined to be like that, and now changing it isn't possible any more because it wouldn't be backwards-compatible and might break stuff.

    Your code is easily fixable by introducing a nested scope, though, which is (to my understanding) best practice in those situations:

    #[derive(Debug)]
    struct X<'a>(&'a i32);
    
    impl Drop for X<'_> {
        fn drop(&mut self) {}
    }
    
    fn main() {
        let mut data = vec![1, 2, 3];
        {
            let x = X(&data[0]);
            println!("{:?}", x);
        }
        data.push(4);
    }
    
    X(1)
    

    Alternatively, you could drop it manually:

    #[derive(Debug)]
    struct X<'a>(&'a i32);
    
    impl Drop for X<'_> {
        fn drop(&mut self) {}
    }
    
    fn main() {
        let mut data = vec![1, 2, 3];
    
        let x = X(&data[0]);
        println!("{:?}", x);
        drop(x);
    
        data.push(4);
    }
    
    X(1)