About drop call of static object

It seems that a static object's drop method will never get called. Is that true?

struct S(u8);
impl Drop for S {
    fn drop(&mut self) {
        println!("drop {}",self.0);
    }
}

static G : S = S(0);
fn main() {
    S(1);
}

Static items do not call drop at the end of the program.

1 Like

To clarify, it is not the object that is static but the variable. If you have a "mutable static" you can very well drop the object that was created statically and have an object created at run-time end up not being dropped:

use parking_lot::{Mutex, const_mutex};

struct S(u8);
impl Drop for S {
    fn drop(&mut self) {
        println!("drop {}",self.0);
    }
}

static G: Mutex<S> = const_mutex(S(0));

fn main() {
    *G.lock() = S(1);
}

(playground)

2 Likes

Interesting. What is the rationale for this? Does C++ have similar behavior?

I think it's because it's (very) hard to do it:

  • "right" (no way to trigger UB, independently of the order of drops, multiple threads, and things like that;

  • cross-platform;

  • with negligible runtime cost.


In practice, you can try you own attempt at this, using https://docs.rs/ctor's #[dtor], and by wrapping the static in an Option:

use ::ctor::dtor;
use ::parking_lot::{const_rwlock, RwLock};

struct S { /* ... */ }
impl Drop for S {
    fn drop (self: &'_ mut Self)
    {
        // this could try to access `G`; guarding about all these cases is tough
        println!("drop");
    }
}

static G: RwLock<Option<S>> = const_rwlock(Some({
    #[dtor]
    fn drop_on_shutdown ()
    {
        *G.write() = None;
    }

    S { /* ... */ }
}));
  • Demo

    asciicast


Note that a far simpler option, if you are not dealing with multi-threaded code, is to use thread_local! statics, which Rust will "try to drop" (best effort):

struct S { /* ... */ }
impl Drop for S {
    fn drop (self: &'_ mut Self)
    {
        // this could try to access `G`; guarding about all these case is tough
        let s = format!("drop from thread: {:?}\0", ::std::thread::current().id());
        unsafe {
            // May be called on main program shutdown,
            // when `println!` isn't available
            ::libc::puts(s.as_bytes().as_ptr().cast());
        }        
    }
}

thread_local! {
    static G: S = S { /* ... */ };
}

fn main ()
{
    G.with(|_| ());
    let _ = ::std::thread::spawn(|| {
        G.with(|_| ());
    }).join();
}
  • Playground

  • That being said, note that for a thread_local! to be instanced, you need to access the value using .with(), since the thread-locals are lazily initialized.


Exercise

Implement a macro with an API identical to thread_local!'s but with the semantics of a global var:

droppable_static! {
    static G: S = S { ... }
}

// Users may call `.with(|g| ...)` where the closure would have
// a signature of `impl FnOnce(&'_ S) -> _`

// And `G` is dropped on program shutdown.
1 Like