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?
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?
Try this crate: https://crates.io/crates/nodrop
That's an interesting trick. Thanks!
There's also https://doc.rust-lang.org/nightly/std/mem/union.ManuallyDrop.html, but it's currently unstable.
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.
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
.
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.
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.
By "overhead", do you mean the ptr::write
of Flag::Dropped(0)
in drop
?
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.
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).
Ok, good points (layout in particular).
I'm curious to ask why do you need nodrop...?