Why Drop trait is only executed at the end of the scope, instead of after the last use?
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)


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?
