How does Rust know when to drop values?


#1

When I assign a new value to a variable, it seems that Rust knows whether the variable currently holds a value and, if so, drops the value. E.g.,

#[derive(Debug)]
struct Foo(usize);

impl Drop for Foo {
    fn drop(&mut self) {
        println!("{:?} dropped", self);
    }
}

fn main() {
    let mut x: Foo;
    let b = read_from_stdin();
    if b {
        x = Foo(1);
    }
    x = Foo(2);
}

My question is: how does Rust know whether a variable holds a value? Because this has to be done at runtime in the example above, does Rust place some marker in x when x is uninitialized? Would this incur space and runtime costs?


#2

My understanding is the compiler will add a call to drop when it sees that a value is going out of scope. This can be determined at compile time. Imagine if you manually called drop in all the places that Foo goes out of scope, the compiler is doing this for you. So in the example of your if branch, the compiler will add a drop call before the new assignment. I’m not familiar with rust internals but this is how it works in other languages.


#3

Yes, there is a so-called “drop flag” that tracks whether a value is uninitialized/dropped or holds a value. If I understand it correctly, it doesn’t have a major performance/memory impact – but still has some. Read more here.

When MIR goes stable, there will be a new implementation of drop flags that (mostly) has the same behaviour but allows for aggressive optimizations. Read more here.


#4

When I have seen Rust at first I have assumed it performs the eager drop, and keeps no run time information about when and what to drop. Much later I’ve read why the eager drop causes troubles if you add an unsafe block inside your function. Still, I haven’t understood why Rust isn’t using the eager drop in functions that don’t contain unsafe blocks, such functions are quite common. Is is this left as future compiler optimization?


#5

See (or ask) here and here. In particular, this comment pretty much sums it up:

It’s required to provide scope-based RAII like D and C++. The lifetime of variables being tied to scopes of what nearly everyone will expect since that’s also what the lifetime system assumes and is how it works in other languages. Extending variable lifetimes on borrows like the eager drop proposal would change them from a compile-time check to a feature with runtime impact along with making correct low-level code more difficult to write.

The need for a dynamic drop flag will only occur when there is a conditional early drop and it can be avoided by adding an explicit drop call in the branch to make it unconditional. It will hardly have any performance impact when it isn’t optimized out usually will be, so that micro-optimization isn’t important.


#6

Thank you all. Is there any way to test if a variable has been initialized at runtime?


#7

No, and I don’t think you’ll gain much even if there was. The compiler won’t let you access the value if it’s not sure it was initialized, even if you’re sure (because you know some internal logic). It is good at understanding simple cases though.