سؤال

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:

  1. Why is this necessary, rather than just letting t fall out of scope and be destroyed?

  2. Why does forget(t) not result in a memory leak?

هل كانت مفيدة؟

المحلول

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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top