Reputation: 35136
There's some code in the standard library along the lines of:
/**
* Swap the values at two mutable locations of the same type, without
* deinitialising or copying either one.
*/
#[inline]
pub fn swap<T>(x: &mut T, y: &mut T) {
unsafe {
// Give ourselves some scratch space to work with
let mut t: T = uninit();
// Perform the swap, `&mut` pointers never alias
ptr::copy_nonoverlapping_memory(&mut t, &*x, 1);
ptr::copy_nonoverlapping_memory(x, &*y, 1);
ptr::copy_nonoverlapping_memory(y, &t, 1);
// y and t now point to the same thing, but we need to completely forget `t`
// because it's no longer relevant.
cast::forget(t);
}
}
In fact, this 'create temporary scratch space and then forget it' pattern turns up several times.
According to the docs intrinsics::forget()
takes ownership but doesn't destroy a value, effectively forgetting the target.
Two pretty simple questions:
Why is this necessary, rather than just letting t
fall out of scope and be destroyed?
Why does forget(t)
not result in a memory leak?
Upvotes: 1
Views: 339
Reputation: 90742
If t
is permitted to fall out of scope, it will be destroyed. That is problematic if the type has a destructor with side-effects; for example, assume that we have a destructor on a file that closes the file handle contained in itself. This would mean that on a swap
call, one of the file handles would be closed, which is certainly undesirable. Any ~T
has a destructor also: it frees the memory. If you were to immediately run the destructor, the memory would be freed, and so you would have a use-after-free/double-free bug.
forget(t)
does not of itself result in a memory leak because inside forget
, it takes its parameter by value, on the stack. Thus, when it returns, the stack memory is freed. If you were to forget a ~T
, that ~T
would indeed leak memory; but that is not what's happening in this case at all, even if you are swapping with T
as ~U
, because of the semantics: t
is simply scratch space; just before the cast::forget(t)
call, there is actually unsoundness, because the same memory is addressed by two owned pointers; that's why one is simply forgotten without running the destructor.
The point of the matter is that forget
should only be used where you are moving a value about and so something that will run its destructor does actually still exist. You shouldn't use it in any other situation, or you can leak memory.
Upvotes: 3