Drop order of non-`Drop` types

⚓ Rust    📅 2026-01-04    👤 surdeus    👁️ 1      

surdeus

I am confused by how adding impl Drop for Foo breaks the following code snippet:

use std::fmt::Debug;

#[derive(Debug)]
struct Foo<'a>(Option<&'a u32>);

impl<'a> Drop for Foo<'a> {
    fn drop(&mut self) {
        println!("{self:?}")
    }
}

fn main() {
    let mut foo = Foo(None);
    let a = 1;
    foo.0 = Some(&a);
}
error[E0597]: `a` does not live long enough
  --> src/main.rs:15:18
   |
14 |     let a = 1;
   |         - binding `a` declared here
15 |     foo.0 = Some(&a);
   |                  ^^ borrowed value does not live long enough
16 | }
   | -
   | |
   | `a` dropped here while still borrowed
   | borrow might be used here, when `foo` is dropped and runs the `Drop` code for type `Foo`
   |
   = note: values in a scope are dropped in the opposite order they are defined

I am aware of the reverse drop order. I also understand that it is not desirable for the compiler to automatically reorder the destructors (perhaps toplogically) because side effects of drop() may rely on the deterministic drop order for correctness.

I read the reference like this:

  • if T: Drop, then the compiler implicitly adds a call to T::drop() at the end of the scope, in the order described.
  • if T is not Drop, it's a no-op, the value just goes out of scope, and the memory will eventually be overwritten.

So I would have expected that the borrow checker see this code:

fn main() {
    let mut foo = Foo(None);
    let a = 1;
    foo.0 = Some(&a);
    // drop(a);  // not actually called, since Option<u32> is not Drop
    drop(foo);
}

This would have borrow-checked fine!
But the error message shows that this is not what the borrow checker sees. It clearly sees an actual drop of a.

I have a hard time building a mental model of why the drop order of non-Drop types only matters when there's another Drop type.

5 posts - 4 participants

Read full topic

🏷️ Rust_feed