What is the purpose of std::mem::ManuallyDrop and when would you use it instead of std::mem::forget?

Rust's ownership system guarantees that destructors run when values go out of scope, but certain advanced patterns require precise control over when—or if—destructors execute. ManuallyDrop and mem::forget both prevent destructor runs, but they serve different purposes and have distinct safety implications.

The Default Behavior: RAII and Drop

In normal Rust code, the compiler automatically inserts destructor calls when values go out of scope:

struct Resource {
    name: String,
}
 
impl Drop for Resource {
    fn drop(&mut self) {
        println!("Dropping: {}", self.name);
    }
}
 
fn example() {
    let r = Resource { name: "test".to_string() };
    // When example() returns, the compiler calls r.drop()
}

This RAII (Resource Acquisition Is Initialization) pattern is fundamental to Rust's safety guarantees. But sometimes you need to prevent this automatic behavior.

mem::forget: Consuming Without Dropping

std::mem::forget takes ownership of a value and "forgets" it—the destructor never runs:

use std::mem;
 
struct Resource {
    name: String,
}
 
impl Drop for Resource {
    fn drop(&mut self) {
        println!("Dropping: {}", self.name);
    }
}
 
fn main() {
    let r = Resource { name: "test".to_string() };
    mem::forget(r);
    // No output - destructor never runs
}

mem::forget is marked safe because it's impossible to prevent in safe Rust anyway (you could create a reference cycle with Rc, for instance). The function consumes the value and leaves nothing behind—the memory may or may not be reclaimed depending on the type.

ManuallyDrop: Wrapper Type for Controlled Destruction

ManuallyDrop wraps a value and inhibits automatic destructor calls, but allows you to manually trigger destruction:

use std::mem::ManuallyDrop;
 
struct Resource {
    name: String,
}
 
impl Drop for Resource {
    fn drop(&mut self) {
        println!("Dropping: {}", self.name);
    }
}
 
fn main() {
    let mut wrapped = ManuallyDrop::new(Resource { 
        name: "test".to_string() 
    });
    
    // The destructor does NOT run when `wrapped` goes out of scope
    
    // But you can manually drop it:
    unsafe {
        ManuallyDrop::drop(&mut wrapped);
    }
    // Output: Dropping: test
}

The key difference: ManuallyDrop keeps the value accessible and allows controlled destruction, while mem::forget consumes and discards the value entirely.

When to Use ManuallyDrop

Implementing Custom Container Types

ManuallyDrop is essential when building types that manage their own memory layout:

use std::mem::ManuallyDrop;
use std::ptr;
 
pub struct Vec<T> {
    ptr: *mut T,
    len: usize,
    capacity: usize,
}
 
impl<T> Drop for Vec<T> {
    fn drop(&mut self) {
        // Drop each element
        for i in 0..self.len {
            unsafe {
                ptr::drop_in_place(self.ptr.add(i));
            }
        }
        // Deallocate buffer
        let layout = std::alloc::Layout::array::<T>(self.capacity).unwrap();
        unsafe {
            std::alloc::dealloc(self.ptr as *mut u8, layout);
        }
    }
}

Wait, that example shows drop_in_place. Here's a better example showing ManuallyDrop in a union or custom smart pointer:

use std::mem::ManuallyDrop;
 
pub struct CustomBox<T> {
    ptr: *mut ManuallyDrop<T>,
}
 
impl<T> CustomBox<T> {
    pub fn new(value: T) -> Self {
        let boxed = Box::new(ManuallyDrop::new(value));
        CustomBox {
            ptr: Box::into_raw(boxed),
        }
    }
    
    pub fn into_inner(self) -> T {
        let boxed = unsafe { Box::from_raw(self.ptr) };
        ManuallyDrop::into_inner(*boxed)
        // Destructor for CustomBox doesn't run - we consumed self
    }
}
 
impl<T> Drop for CustomBox<T> {
    fn drop(&mut self) {
        unsafe {
            // Drop the ManuallyDrop wrapper, which drops the inner value
            ptr::drop_in_place(self.ptr);
            // Free the memory
            let _ = Box::from_raw(self.ptr);
        }
    }
}

Actually, let me provide clearer, more practical examples:

Implementing a Union-Like Type

use std::mem::ManuallyDrop;
 
enum StringOrVec {
    String(ManuallyDrop<String>),
    Vec(ManuallyDrop<Vec<u8>>),
}
 
impl Drop for StringOrVec {
    fn drop(&mut self) {
        // Manually drop the active variant
        match self {
            StringOrVec::String(s) => unsafe { ManuallyDrop::drop(s); }
            StringOrVec::Vec(v) => unsafe { ManuallyDrop::drop(v); }
        }
    }
}

Transferring Ownership Without Moving

When you need to transfer ownership but can't use move because the source must remain valid:

use std::mem::ManuallyDrop;
 
struct Buffer {
    data: ManuallyDrop<Vec<u8>>,
}
 
impl Buffer {
    /// Take ownership of the internal Vec, leaving an empty Vec in its place
    fn take_data(&mut self) -> Vec<u8> {
        // SAFETY: We immediately replace with a valid value
        let data = unsafe {
            let data = ManuallyDrop::take(&mut self.data);
            self.data = ManuallyDrop::new(Vec::new());
            data
        };
        data
    }
    
    fn get_data(&self) -> &Vec<u8> {
        &self.data
    }
}
 
impl Drop for Buffer {
    fn drop(&mut self) {
        unsafe {
            ManuallyDrop::drop(&mut self.data);
        }
    }
}

Building a Self-Referential Structure

While self-referential structures are generally unsafe in Rust, ManuallyDrop can help manage the destruction order:

use std::mem::ManuallyDrop;
use std::pin::Pin;
 
struct SelfReferential {
    data: String,
    // Points into `data`
    pointer: *const str,
}
 
impl SelfReferential {
    fn new(s: String) -> Pin<Box<Self>> {
        let mut boxed = Box::pin(SelfReferential {
            data: s,
            pointer: std::ptr::null(),
        });
        
        // SAFETY: We're pinned, so the data won't move
        let self_ptr: *const str = &boxed.data as *const str;
        let len = boxed.data.len();
        
        unsafe {
            let mut_ref = Pin::as_mut(&mut boxed);
            let mut_self = mut_ref.get_unchecked_mut();
            mut_self.pointer = std::ptr::slice_from_raw_parts(self_ptr as *const u8, len);
        }
        
        boxed
    }
}

Actually, let me simplify this to show ManuallyDrop more directly:

Implementing a Slot-Based Allocator

use std::mem::ManuallyDrop;
use std::ptr::NonNull;
 
pub struct Slot<T, const N: usize> {
    data: [ManuallyDrop<Option<T>>; N],
    used: [bool; N],
}
 
impl<T, const N: usize> Slot<T, N> {
    pub fn new() -> Self {
        Slot {
            data: std::array::from_fn(|_| ManuallyDrop::new(None)),
            used: [false; N],
        }
    }
    
    pub fn insert(&mut self, value: T) -> Option<usize> {
        for (i, used) in self.used.iter_mut().enumerate() {
            if !*used {
                // SAFETY: We're initializing a previously unused slot
                unsafe {
                    ManuallyDrop::drop(&mut self.data[i]);
                }
                self.data[i] = ManuallyDrop::new(Some(value));
                *used = true;
                return Some(i);
            }
        }
        None
    }
    
    pub fn remove(&mut self, index: usize) -> Option<T> {
        if index >= N || !self.used[index] {
            return None;
        }
        
        self.used[index] = false;
        // SAFETY: We checked that this slot was used
        let value = unsafe { ManuallyDrop::take(&mut self.data[index]) };
        self.data[index] = ManuallyDrop::new(None);
        value
    }
}
 
impl<T, const N: usize> Drop for Slot<T, N> {
    fn drop(&mut self) {
        for i in 0..N {
            if self.used[i] {
                unsafe {
                    ManuallyDrop::drop(&mut self.data[i]);
                }
            }
        }
    }
}

When to Use mem::forget

mem::forget is appropriate when you explicitly want to leak memory or resources:

FFI Ownership Transfer

When passing ownership to C code that will manage the resource:

use std::mem;
 
struct OpaqueHandle {
    data: Vec<u8>,
}
 
#[no_mangle]
pub extern "C" fn create_handle(data: Vec<u8>) -> *mut OpaqueHandle {
    let handle = Box::new(OpaqueHandle { data });
    Box::into_raw(handle)
}
 
#[no_mangle]
pub extern "C" fn destroy_handle(handle: *mut OpaqueHandle) {
    if !handle.is_null() {
        unsafe {
            // C code is giving ownership back to us
            let _ = Box::from_raw(handle);
            // Box is dropped here
        }
    }
}
 
// Alternative: transfer Vec ownership to C without destroying
#[no_mangle]
pub extern "C" fn take_vec_data(vec: Vec<u8>) -> *const u8 {
    let ptr = vec.as_ptr();
    let len = vec.len();
    
    // C code now owns this memory and will free it
    // We forget the Vec so its destructor doesn't run
    mem::forget(vec);
    
    ptr
}

Implementing IntoRaw Patterns

use std::mem;
use std::ptr::NonNull;
 
pub struct OwningSlice<T> {
    ptr: NonNull<T>,
    len: usize,
}
 
impl<T> OwningSlice<T> {
    pub fn from_vec(vec: Vec<T>) -> Self {
        let len = vec.len();
        let ptr = NonNull::new(vec.as_ptr() as *mut T).unwrap();
        
        // Prevent Vec's destructor from running
        // We now own the allocation
        mem::forget(vec);
        
        OwningSlice { ptr, len }
    }
    
    pub fn into_vec(self) -> Vec<T> {
        // Reconstruct the Vec
        unsafe {
            Vec::from_raw_parts(self.ptr.as_ptr(), self.len, self.len)
        }
        // Don't run our destructor - Vec owns it now
        mem::forget(self);
    }
}
 
impl<T> Drop for OwningSlice<T> {
    fn drop(&mut self) {
        unsafe {
            // Drop all elements and free the memory
            let _ = Vec::from_raw_parts(self.ptr.as_ptr(), self.len, self.len);
        }
    }
}

Intentional Leaks

Sometimes leaking is the right behavior:

use std::mem;
 
fn leak_for_static_lifetime<T>(value: T) -> &'static T {
    // Box::leak is usually better for this, but mem::forget works too
    let boxed = Box::new(value);
    let reference: &'static T = Box::leak(boxed);
    reference
}
 
fn configuration_leak() {
    // Configuration that should live for the entire program
    let config = load_config();
    
    // Intentionally leak so it's always accessible
    mem::forget(config);
}

Key Differences Summary

Aspect ManuallyDrop mem::forget
Value accessibility Remains accessible Consumed, inaccessible
Destructor control Can manually drop later Never runs
Memory Value remains in place Depends on type
Use case Build abstractions Transfer/leak ownership
Safety story Clear ownership model Implicit ownership transfer

The Safety Contract

Both require careful reasoning about ownership:

use std::mem::{self, ManuallyDrop};
 
struct FileDescriptor {
    fd: i32,
}
 
impl Drop for FileDescriptor {
    fn drop(&mut self) {
        // Close the file descriptor
        unsafe { libc::close(self.fd); }
    }
}
 
fn demonstrate_difference() {
    // ManuallyDrop: we can still use the value
    let mut manual = ManuallyDrop::new(FileDescriptor { fd: 3 });
    
    // Value is still there
    println!("FD: {}", manual.fd);
    
    // We control when it drops
    unsafe { ManuallyDrop::drop(&mut manual); }
    // manual.fd is now invalid - don't use it!
    
    // mem::forget: value is gone
    let forgotten = FileDescriptor { fd: 4 };
    mem::forget(forgotten);
    // forgotten is gone - fd 4 will never be closed
    // This is a resource leak!
}

Practical Guidelines

Use ManuallyDrop when:

  • Building custom containers or smart pointers
  • You need precise control over when destruction occurs
  • The value must remain accessible after preventing auto-drop
  • Implementing custom Drop with non-trivial destruction order

Use mem::forget when:

  • Transferring ownership to external code (FFI)
  • Intentionally leaking memory for 'static lifetime
  • The value should be completely discarded without running its destructor
  • Converting from a managing type to a raw pointer + ownership transfer

Prefer alternatives when possible:

  • Box::leak() for intentional leaks
  • Box::into_raw() / Box::from_raw() for raw pointer conversions
  • Rc::into_raw() / Arc::into_raw() for reference-counted types

Synthesis

ManuallyDrop and mem::forget both prevent automatic destructor execution but serve fundamentally different purposes. ManuallyDrop wraps a value to inhibit auto-drop while keeping the value accessible for manual destruction—it's a tool for building abstractions that need precise control over destruction timing. mem::forget consumes a value entirely, preventing any future access or destruction—it's a tool for ownership transfer and intentional resource leaks.

The choice depends on whether you need continued access to the value. If you're implementing a container, smart pointer, or any type that manages destruction itself, use ManuallyDrop. If you're handing ownership to external code or intentionally leaking, mem::forget is appropriate. Both require unsafe code or unsafe-adjacent reasoning and should be used sparingly, with clear documentation of the ownership invariants being maintained.