How to avoid calling destructor


#1

If there something like NoDropCell<T>? A struct which holds a value of T, but doesn’t call destructor of T on drop of NoDropCell<T>?

Or how can I implement it myself?


#2

Try this crate: https://crates.io/crates/nodrop


#3

That’s an interesting trick. Thanks!


#4

There’s also https://doc.rust-lang.org/nightly/std/mem/union.ManuallyDrop.html, but it’s currently unstable.


#5

The easiest way to dispose of something without calling its destructor is to call mem::forget():

let v = vec![1; 100];
mem::forget(v);  // whoops, memory leak

In the example above, the few bytes on the stack that struct Vec takes up will be freed, but since the destructor will not be called, the buffer allocated on the heap will be leaked.

If you don’t want to call mem::forget() manually, you can implement a zero-cost wrapper type like this:

use std::mem;

// public for simplicity, implement Deref/DerefMut instead in real code
struct NoDropCell<T>(pub T);

impl<T> Drop for NoDropCell<T> {
    fn drop(&mut self) {
        mem::forget(self.0);
    }
}

and then use it like this:

let v = vec![1; 100];
let mut ndv = NoDropCell(v);
ndv.0.push(42);
// end of block -- dropping ndv, leaking the heap buffer

As @sfackler mentioned, there’s a standard implementation of the above in stdlib called ManuallyDrop, but it’s not yet stable.

Interestingly, mem::forget() is now implemented in terms of ManuallyDrop instead of it being the other way around as I did it above. Before ManuallyDrop existed, mem::forget() was implemented via a compiler intrinsic.

How is ManuallyDrop implemented then, you may wonder, if it doesn’t wrap mem::forget()? The answer is it uses untagged unions, a new language feature primarily intended for FFI but also enabling a few interesting tricks like this one.


#6

If you don’t want to call mem::forget() manually, you can implement a zero-cost wrapper type like this:

Unfortunately, you can’t. Error is

error[E0509]: cannot move out of type `NoDropCell<T>`, which implements the `Drop` trait
  --> <anon>:15:21
   |
15 |         mem::forget(self.0);
   |                     ^^^^^^ cannot move out of here

error: aborting due to previous error

As far as I can see, NoDropCell cannot be implemented without union.


#7

It can be implemented without union - the nodrop crate linked above does it using an inner enum that’s “flipped” upon drop, which inhibits a drop call on the inner value.


#8

It can be implemented without union - the nodrop crate linked above does it using an inner enum that’s “flipped” upon drop, which inhibits a drop call on the inner value.

Well, I mean without overhead. nodrop adds overhead.


#9

By “overhead”, do you mean the ptr::write of Flag::Dropped(0) in drop?


#10

Ooops, you’re right.

We can get away by using an Option:

use std::mem;

// public for simplicity, implement Deref/DerefMut instead in real code
struct NoDropCell<T>(pub Option<T>);

impl<T> Drop for NoDropCell<T> {
    fn drop(&mut self) {
        mem::forget(self.0.take());
    }
}

Caller-side API will be a lot more verbose though, but you’d use impl Deref/DerefMut anyway.

But yeah, it won’t be zero-cost anymore. It could have been done with transmute() and a byte array if const fn was stable.


#11

By “overhead”, do you mean the ptr::write of Flag::Dropped(0) in drop?

Memory overhead of storing discriminant. (Which also makes memory layout of such NoDropCell<T> incompatible with T, BTW).


#12

Ok, good points (layout in particular).


#13

I’m curious to ask why do you need nodrop…?