Consider this piece of code that uses ManuallyDrop<T>
and typenum::consts::{U0, U1}
.
/*
[dependencies]
typenum = "1.17.0"
*/
use core::{
marker::PhantomData,
mem::{forget, ManuallyDrop},
ops::{Add, Deref, Drop},
};
pub use typenum;
use typenum::{Sum, Unsigned, U0, U1};
struct Counter<T, N: Unsigned + Add<U1> = U0>(ManuallyDrop<T>, PhantomData<N>);
impl<T, N> Counter<T, N>
where
N: Unsigned + Add<U1>,
U1: Add<N>,
Sum<N, U1>: Unsigned + Add<U1>,
{
fn new(t: T) -> Self {
Self(ManuallyDrop::new(t), PhantomData)
}
fn count_up(mut self) -> Counter<T, Sum<N, U1>>
where
Sum<N, U1>: Add<U1> + Unsigned,
N: Unsigned + Add<U1>,
{
println!(
"Inside `Counter::count_up`, `N` before increment: {:?}",
N::to_usize()
);
let inner = ManuallyDrop::new(unsafe { ManuallyDrop::take(&mut self.0) });
forget(self);
println!(
"Inside `Counter::count_up`, `N` after increment: {:?}\n",
Sum::<N, U1>::to_usize()
);
Counter(inner, PhantomData::<Sum<N, U1>>)
}
}
impl<T, N> Drop for Counter<T, N>
where
N: Add<U1> + Unsigned,
{
fn drop(&mut self) {
println!(
"Inside `Drop` implementation of Counter<T, {:?}>",
N::to_u64()
);
// Need to take out `T` from the `ManuallyDrop<T>` wrapper
let _ = unsafe { ManuallyDrop::take(&mut self.0) };
}
}
fn main() {
use core::sync::atomic::{AtomicUsize, Ordering};
static NUM_DROPS: AtomicUsize = AtomicUsize::new(0);
struct DetectDrop;
impl Drop for DetectDrop {
fn drop(&mut self) {
NUM_DROPS.fetch_add(1, Ordering::Relaxed);
}
}
let counter: Counter<DetectDrop> = Counter::new(DetectDrop);
{
let counter = counter.count_up(); // `N` is now 1;
// Inside `Counter::count_up`, `N` before increment: 0
// Inside `Counter::count_up`, `N` after increment: 1
assert_eq!(NUM_DROPS.load(Ordering::Relaxed), 0usize);
let counter = counter.count_up(); // `N` is now 2;
// Inside `Counter::count_up`, `N` before increment: 1
// Inside `Counter::count_up`, `N` after increment: 2
let counter = counter.count_up(); // `N` is now 3;
let counter = counter.count_up(); // `N` is now 4;
let counter = counter.count_up(); // `N` is now 5;
// Why is this `0`?
assert_eq!(NUM_DROPS.load(Ordering::Relaxed), 0usize);
}
assert_eq!(NUM_DROPS.load(Ordering::Relaxed), 1usize);
}
Astonishingly, on the line of assert_eq!(NUM_DROPS.load(Ordering::Relaxed), 0usize);
within the braced scope, NUM_DROPS
is actually 0usize
, signifies that Drop
trait's fn drop
wasn't called despite reaching the end of the scope.
In my crate sosecrets-rs
, for the testing that the drop
function is indeed called correctly code, I noted that NUM_DROPS
is 1usize at the end of the scope.
My question is why there are differences in when drop
is called in the two scenarios? Is it because the compiler does not guarantee when drop
is called?