How do I work with mem::ManuallyDrop in Rust?

Walkthrough

ManuallyDrop is a wrapper type that prevents automatic drop calls. It's used when you need explicit control over when a value is destroyed, such as in FFI, implementing custom smart pointers, or optimizing performance-critical code.

Key concepts:

  • No automatic drop — The inner value won't be dropped automatically
  • Unsafe to forget — You must manually drop or extract the value
  • Zero overhead — Same memory layout as T
  • Drop order control — Explicit control over destruction order

When to use ManuallyDrop:

  • Implementing custom smart pointers (Rc, Arc, Box)
  • FFI with non-Rust memory management
  • Performance optimizations avoiding redundant drops
  • Union types (required for safety)
  • Transferring ownership to another location

Safety considerations:

  • Forgetting to drop a ManuallyDrop causes memory leaks
  • Dropping twice is undefined behavior
  • Use drop_in_place or take for safe cleanup

Code Examples

Basic ManuallyDrop Usage

use std::mem::ManuallyDrop;
 
fn main() {
    // Wrap a value in ManuallyDrop
    let mut value = ManuallyDrop::new(String::from("Hello, World!"));
    
    println!("Value: {}", value);
    
    // Access the inner value
    println!("Length: {}", value.len());
    
    // The value is NOT dropped when 'value' goes out of scope!
    // We must manually drop it:
    unsafe {
        ManuallyDrop::drop(&mut value);
    }
    
    // After drop, 'value' must not be used!
}

Preventing Automatic Drop

use std::mem::ManuallyDrop;
 
struct Resource {
    name: String,
    data: Vec<u8>,
}
 
impl Drop for Resource {
    fn drop(&mut self) {
        println!("Dropping resource: {}", self.name);
    }
}
 
fn main() {
    {
        let _normal = Resource {
            name: String::from("normal"),
            data: vec![1, 2, 3],
        };
        // Will be dropped at end of scope
    }
    println!("---");
    
    {
        let _manual = ManuallyDrop::new(Resource {
            name: String::from("manual"),
            data: vec![4, 5, 6],
        });
        // Will NOT be dropped at end of scope!
        // Memory leak!
    }
    println!("After manual scope");
}

Proper Cleanup with ManuallyDrop

use std::mem::ManuallyDrop;
 
fn main() {
    let mut manual = ManuallyDrop::new(String::from("Important data"));
    
    // Method 1: Use ManuallyDrop::drop
    unsafe {
        ManuallyDrop::drop(&mut manual);
    }
    // After this, manual is invalid - don't use it!
    
    // Method 2: Take the value out
    let mut manual2 = ManuallyDrop::new(String::from("More data"));
    let extracted: String = ManuallyDrop::take(&mut manual2);
    println!("Extracted: {}", extracted);
    // extracted will be dropped normally
    // manual2 is now empty - don't use it!
}

Implementing a Smart Pointer

use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::ptr::NonNull;
 
struct MyBox<T> {
    ptr: NonNull<T>,
}
 
impl<T> MyBox<T> {
    fn new(value: T) -> Self {
        let boxed = Box::new(value);
        Self {
            ptr: unsafe { NonNull::new_unchecked(Box::into_raw(boxed)) },
        }
    }
}
 
impl<T> Deref for MyBox<T> {
    type Target = T;
    
    fn deref(&self) -> &T {
        unsafe { self.ptr.as_ref() }
    }
}
 
impl<T> Drop for MyBox<T> {
    fn drop(&mut self) {
        // Need to manually drop the Box
        unsafe {
            drop(Box::from_raw(self.ptr.as_ptr()));
        }
    }
}
 
// A more complex example with reference counting
struct RcInner<T> {
    value: ManuallyDrop<T>,
    ref_count: usize,
}
 
pub struct SimpleRc<T> {
    inner: NonNull<RcInner<T>>,
}
 
impl<T> SimpleRc<T> {
    pub fn new(value: T) -> Self {
        let inner = Box::new(RcInner {
            value: ManuallyDrop::new(value),
            ref_count: 1,
        });
        Self {
            inner: unsafe { NonNull::new_unchecked(Box::into_raw(inner)) },
        }
    }
    
    pub fn get(&self) -> &T {
        unsafe { &self.inner.as_ref().value }
    }
}
 
impl<T> Clone for SimpleRc<T> {
    fn clone(&self) -> Self {
        unsafe {
            (*self.inner.as_ptr()).ref_count += 1;
        }
        Self { inner: self.inner }
    }
}
 
impl<T> Drop for SimpleRc<T> {
    fn drop(&mut self) {
        unsafe {
            let inner = self.inner.as_mut();
            inner.ref_count -= 1;
            
            if inner.ref_count == 0 {
                // Drop the value first
                ManuallyDrop::drop(&mut inner.value);
                // Then free the allocation
                drop(Box::from_raw(self.inner.as_ptr()));
            }
        }
    }
}
 
fn main() {
    let rc1 = SimpleRc::new(String::from("Hello"));
    println!("Value: {}", rc1.get());
    
    let rc2 = rc1.clone();
    println!("Cloned value: {}", rc2.get());
    
    // Both will be dropped properly
}

FFI with Manual Memory Management

use std::mem::ManuallyDrop;
use std::ffi::CString;
use std::os::raw::c_char;
 
// Simulated FFI resource
struct FfiBuffer {
    ptr: *mut c_char,
    size: usize,
}
 
impl FfiBuffer {
    fn new(data: &[u8]) -> Self {
        let c_string = CString::new(data).unwrap();
        Self {
            ptr: c_string.into_raw(),
            size: data.len(),
        }
    }
    
    fn as_bytes(&self) -> &[u8] {
        unsafe { std::slice::from_raw_parts(self.ptr as *const u8, self.size) }
    }
}
 
// Wrapper that manages FFI memory
struct ManagedFfiBuffer {
    // Use ManuallyDrop to control when we free the FFI resource
    buffer: ManuallyDrop<FfiBuffer>,
}
 
impl ManagedFfiBuffer {
    fn new(data: &[u8]) -> Self {
        Self {
            buffer: ManuallyDrop::new(FfiBuffer::new(data)),
        }
    }
    
    fn as_bytes(&self) -> &[u8] {
        self.buffer.as_bytes()
    }
    
    // Transfer ownership to FFI (don't drop on our side)
    fn into_raw(mut self) -> *mut c_char {
        let ptr = self.buffer.ptr;
        // Don't drop the buffer - FFI owns it now
        std::mem::forget(self);
        ptr
    }
}
 
impl Drop for ManagedFfiBuffer {
    fn drop(&mut self) {
        unsafe {
            // Free the CString
            let c_string = CString::from_raw(self.buffer.ptr);
            drop(c_string);
            // Mark as dropped (optional, for documentation)
            ManuallyDrop::drop(&mut self.buffer);
        }
    }
}
 
fn main() {
    let managed = ManagedFfiBuffer::new(b"Hello FFI");
    println!("Buffer: {:?}", managed.as_bytes());
    // Will be dropped properly
}

Union Types with ManuallyDrop

use std::mem::ManuallyDrop;
 
// A tagged union using ManuallyDrop
enum Tag {
    Int,
    Float,
    String,
}
 
union Value {
    int: i64,
    float: f64,
    string: ManuallyDrop<String>,  // Must use ManuallyDrop for types with Drop
}
 
struct TaggedValue {
    tag: Tag,
    value: Value,
}
 
impl TaggedValue {
    fn int(value: i64) -> Self {
        Self {
            tag: Tag::Int,
            value: Value { int: value },
        }
    }
    
    fn float(value: f64) -> Self {
        Self {
            tag: Tag::Float,
            value: Value { float: value },
        }
    }
    
    fn string(s: String) -> Self {
        Self {
            tag: Tag::String,
            value: Value { string: ManuallyDrop::new(s) },
        }
    }
    
    fn as_int(&self) -> Option<i64> {
        match self.tag {
            Tag::Int => Some(unsafe { self.value.int }),
            _ => None,
        }
    }
    
    fn as_float(&self) -> Option<f64> {
        match self.tag {
            Tag::Float => Some(unsafe { self.value.float }),
            _ => None,
        }
    }
    
    fn as_string(&self) -> Option<&String> {
        match self.tag {
            Tag::String => Some(unsafe { &self.value.string }),
            _ => None,
        }
    }
}
 
impl Drop for TaggedValue {
    fn drop(&mut self) {
        unsafe {
            match self.tag {
                Tag::String => {
                    ManuallyDrop::drop(&mut self.value.string);
                }
                _ => {} // Int and Float don't need drop
            }
        }
    }
}
 
fn main() {
    let int_val = TaggedValue::int(42);
    let float_val = TaggedValue::float(3.14);
    let string_val = TaggedValue::string(String::from("Hello"));
    
    println!("Int: {:?}", int_val.as_int());
    println!("Float: {:?}", float_val.as_float());
    println!("String: {:?}", string_val.as_string());
}

Avoiding Redundant Drops

use std::mem::ManuallyDrop;
 
// A buffer that might transfer ownership
struct Buffer {
    data: Vec<u8>,
}
 
struct TransferableBuffer {
    // Use ManuallyDrop so we can optionally transfer without double-free
    buffer: ManuallyDrop<Buffer>,
    transferred: bool,
}
 
impl TransferableBuffer {
    fn new(data: Vec<u8>) -> Self {
        Self {
            buffer: ManuallyDrop::new(Buffer { data }),
            transferred: false,
        }
    }
    
    fn data(&self) -> &[u8] {
        &self.buffer.data
    }
    
    // Transfer ownership to another owner
    fn transfer(mut self) -> Buffer {
        self.transferred = true;
        // Take the buffer out - we won't drop it
        ManuallyDrop::take(&mut self.buffer)
    }
}
 
impl Drop for TransferableBuffer {
    fn drop(&mut self) {
        // Only drop if not transferred
        if !self.transferred {
            unsafe {
                ManuallyDrop::drop(&mut self.buffer);
            }
        }
    }
}
 
fn main() {
    // Normal usage - dropped at end of scope
    {
        let buf = TransferableBuffer::new(vec![1, 2, 3]);
        println!("Data: {:?}", buf.data());
    }
    
    // Transfer ownership
    {
        let buf = TransferableBuffer::new(vec![4, 5, 6]);
        let transferred = buf.transfer();
        println!("Transferred data: {:?}", transferred.data);
        // transferred is now responsible for cleanup
    }
}

Delayed Drop Pattern

use std::mem::ManuallyDrop;
use std::collections::VecDeque;
 
struct DelayedDropper {
    pending_drops: VecDeque<ManuallyDrop<Box<dyn std::any::Any>>>,
}
 
impl DelayedDropper {
    fn new() -> Self {
        Self {
            pending_drops: VecDeque::new(),
        }
    }
    
    fn schedule_drop<T: 'static>(&mut self, value: T) {
        self.pending_drops.push_back(ManuallyDrop::new(Box::new(value)));
    }
    
    fn process_one(&mut self) -> bool {
        if let Some(mut value) = self.pending_drops.pop_front() {
            unsafe {
                ManuallyDrop::drop(&mut value);
            }
            true
        } else {
            false
        }
    }
    
    fn process_all(&mut self) {
        while self.process_one() {}
    }
}
 
impl Drop for DelayedDropper {
    fn drop(&mut self) {
        // Clean up any remaining items
        self.process_all();
    }
}
 
fn main() {
    let mut dropper = DelayedDropper::new();
    
    dropper.schedule_drop(String::from("First"));
    dropper.schedule_drop(String::from("Second"));
    dropper.schedule_drop(vec![1, 2, 3]);
    
    println!("Processing drops...");
    dropper.process_one();
    println!("Processed one");
    
    // Remaining items dropped when dropper goes out of scope
}

ManuallyDrop with Read/Write

use std::mem::ManuallyDrop;
 
fn main() {
    let mut value = ManuallyDrop::new(String::from("Hello"));
    
    // Read access (safe)
    println!("Value: {}", *value);
    
    // Modify the inner value (safe)
    value.push_str(", World!");
    println!("Modified: {}", *value);
    
    // Replace the inner value
    let old = std::mem::replace(&mut *value, String::from("New value"));
    println!("Old: {}, New: {}", old, *value);
    
    // old is a normal String and will be dropped
    
    // Clean up
    unsafe {
        ManuallyDrop::drop(&mut value);
    }
}

Slot Allocation Pattern

use std::mem::ManuallyDrop;
use std::mem::MaybeUninit;
 
struct Slot<T> {
    value: ManuallyDrop<T>,
    occupied: bool,
}
 
impl<T> Slot<T> {
    fn uninit() -> Self {
        // This is a placeholder - in real code use MaybeUninit
        unimplemented!("Use MaybeUninit for truly uninitialized slots")
    }
}
 
// A better approach using MaybeUninit + ManuallyDrop
struct SafeSlot<T> {
    value: MaybeUninit<ManuallyDrop<T>>,
    occupied: bool,
}
 
impl<T> SafeSlot<T> {
    fn new() -> Self {
        Self {
            value: MaybeUninit::uninit(),
            occupied: false,
        }
    }
    
    fn put(&mut self, value: T) {
        assert!(!self.occupied, "Slot already occupied");
        self.value.write(ManuallyDrop::new(value));
        self.occupied = true;
    }
    
    fn take(&mut self) -> Option<T> {
        if self.occupied {
            self.occupied = false;
            // SAFETY: We know the slot is occupied
            let manually_drop = unsafe { self.value.assume_init_read() };
            Some(ManuallyDrop::into_inner(manually_drop))
        } else {
            None
        }
    }
    
    fn get(&self) -> Option<&T> {
        if self.occupied {
            // SAFETY: We know the slot is occupied
            Some(unsafe { &*self.value.assume_init_ref() })
        } else {
            None
        }
    }
}
 
impl<T> Drop for SafeSlot<T> {
    fn drop(&mut self) {
        if self.occupied {
            unsafe {
                ManuallyDrop::drop(self.value.assume_init_mut());
            }
        }
    }
}
 
fn main() {
    let mut slot = SafeSlot::new();
    
    assert!(slot.get().is_none());
    
    slot.put(String::from("Hello"));
    println!("Value: {:?}", slot.get());
    
    let taken = slot.take();
    println!("Taken: {:?}", taken);
    
    assert!(slot.get().is_none());
}

Summary

ManuallyDrop Methods:

Method Description Safety
new(value) Wrap value in ManuallyDrop Safe
into_inner(this) Extract value, consuming ManuallyDrop Safe
take(this) Extract value from &mut ManuallyDrop<T> Safe
drop(slot) Manually drop the inner value Unsafe
Deref / DerefMut Access inner value Safe

When to Use ManuallyDrop:

Scenario Appropriate?
Implementing smart pointers āœ… Yes
FFI with manual memory management āœ… Yes
Union variants with Drop types āœ… Required
Transferring ownership āœ… Yes
Avoiding redundant drops āœ… Yes
Normal Rust code āŒ Use automatic drop
When you might forget to drop āŒ Risky

Safety Invariants:

Action Safety
Creating ManuallyDrop Safe
Reading/writing inner value Safe
Calling drop twice āŒ UB (Double drop)
Forgetting to drop āš ļø Memory leak
Using after drop āŒ UB (Use after free)
Using after take āŒ UB (Use after move)

Key Points:

  • ManuallyDrop prevents automatic Drop
  • Use ManuallyDrop::drop(&mut slot) to manually drop
  • Use ManuallyDrop::take(&mut slot) to extract ownership
  • Same memory layout as inner type
  • Required for union types with Drop impls
  • Essential for implementing custom smart pointers
  • Memory leak if you forget to drop
  • Double drop is undefined behavior