What ManuallyDrop does

ManuallyDrop<T> is a transparent wrapper around T that suppresses automatic destruction. The wrapped value still exists in memory, but Rust will not call drop for it when the wrapper goes out of scope.

use std::mem::ManuallyDrop;

struct Session {
    file: ManuallyDrop<std::fs::File>,
}

In this example, Session will not automatically close the file when it is dropped. That means you must explicitly decide when and how the file is closed.

This is different from std::mem::drop, which destroys a value immediately, and from Option<T>, which is often used to model “present or absent” ownership safely. ManuallyDrop is lower-level than both.

When it is useful

Typical use cases include:

  • implementing custom collections or containers
  • managing resources with nonstandard teardown order
  • building FFI-safe wrappers around C APIs
  • avoiding double-drop in advanced ownership patterns
  • storing values that may be conditionally destroyed later

A good rule: if you can model the problem with ordinary ownership, Option, or RAII, prefer that. Reach for ManuallyDrop only when you need explicit destructor control.


The basic API

ManuallyDrop is simple:

  • ManuallyDrop::new(value) wraps a value
  • ManuallyDrop::drop(&mut wrapper) explicitly runs the destructor
  • ManuallyDrop<T> dereferences to T for access
use std::mem::ManuallyDrop;

fn main() {
    let mut value = ManuallyDrop::new(String::from("hello"));

    // Use the inner value normally.
    println!("{}", &*value);

    // Explicitly destroy it.
    unsafe {
        ManuallyDrop::drop(&mut value);
    }
}

The drop method is unsafe because Rust cannot verify that you will not use the value again afterward. Once you manually drop it, the wrapper still exists, but the inner value is logically gone.

Important rule

After calling ManuallyDrop::drop, you must treat the inner value as uninitialized. Any further access is undefined behavior.


A practical example: delayed teardown

Suppose you are building a resource manager that owns a network connection and wants to close it only after flushing logs and metrics in a specific order.

use std::mem::ManuallyDrop;

struct Connection {
    id: u64,
}

impl Connection {
    fn close(self) {
        println!("closing connection {}", self.id);
    }
}

struct Worker {
    conn: ManuallyDrop<Connection>,
    flushed: bool,
}

impl Worker {
    fn new(id: u64) -> Self {
        Self {
            conn: ManuallyDrop::new(Connection { id }),
            flushed: false,
        }
    }

    fn flush(&mut self) {
        println!("flushing work");
        self.flushed = true;
    }

    fn shutdown(mut self) {
        self.flush();

        unsafe {
            ManuallyDrop::drop(&mut self.conn);
        }
    }
}

This pattern lets you separate “logical shutdown” from automatic scope-based destruction. That can be useful when the resource must be closed after some cleanup step that depends on runtime state.

However, this example also shows why ManuallyDrop should be used sparingly: the type now has a custom lifecycle that the compiler cannot enforce for you.


ManuallyDrop versus Option<T>

Many developers reach for ManuallyDrop when Option<T> would be safer and clearer. The two are often interchangeable at a high level, but they serve different goals.

ApproachBest forSafetyNotes
Option<T>values that may be present or absentHighAutomatically handles drop when set to None
ManuallyDrop<T>explicit destructor controlLowerYou must ensure correct manual cleanup
std::mem::forgetintentionally leaking a valueLowSkips destruction entirely

A common pattern is to start with Option<T> and only switch to ManuallyDrop if you need tighter control over memory layout or destructor timing.

Prefer Option<T> when possible

If your goal is simply to “take ownership out” or “disable cleanup after a certain point,” Option<T> is usually enough:

struct Cache {
    file: Option<std::fs::File>,
}

impl Cache {
    fn close(&mut self) {
        self.file.take(); // drops the file if present
    }
}

This is easier to reason about and much harder to misuse.


Building safe wrappers around unsafe internals

A common advanced pattern is to hide ManuallyDrop behind a safe API. The wrapper owns the risk; callers see a normal abstraction.

use std::mem::ManuallyDrop;

pub struct LateDrop<T> {
    value: ManuallyDrop<T>,
    dropped: bool,
}

impl<T> LateDrop<T> {
    pub fn new(value: T) -> Self {
        Self {
            value: ManuallyDrop::new(value),
            dropped: false,
        }
    }

    pub fn into_inner(mut self) -> T {
        self.dropped = true;
        unsafe { ManuallyDrop::into_inner(std::ptr::read(&self.value)) }
    }

    pub fn drop_now(&mut self) {
        if !self.dropped {
            unsafe {
                ManuallyDrop::drop(&mut self.value);
            }
            self.dropped = true;
        }
    }
}

impl<T> Drop for LateDrop<T> {
    fn drop(&mut self) {
        if !self.dropped {
            unsafe {
                ManuallyDrop::drop(&mut self.value);
            }
        }
    }
}

This design is more complex than a normal wrapper, but it illustrates an important principle: if you use ManuallyDrop, encapsulate it. Do not expose raw manual-drop responsibilities to every caller.

Best practice

Keep the unsafe code in a small, well-reviewed module. Provide a safe public interface that makes invalid states unrepresentable.


Common pitfalls

ManuallyDrop is powerful, but several mistakes come up repeatedly.

1. Moving a ManuallyDrop after manual drop

Once the inner value has been dropped, moving the wrapper can be dangerous if the move causes Rust to treat the old bytes as a live value. Avoid any operation that might read or move the inner data after destruction.

2. Dropping twice

Calling ManuallyDrop::drop twice on the same value is a double free if T owns resources.

use std::mem::ManuallyDrop;

fn bad() {
    let mut value = ManuallyDrop::new(String::from("data"));
    unsafe {
        ManuallyDrop::drop(&mut value);
        ManuallyDrop::drop(&mut value); // wrong
    }
}

Track state explicitly if there is any chance of repeated cleanup.

3. Assuming ManuallyDrop makes everything safe

It only disables automatic destruction. It does not make aliasing, borrowing, or initialization rules disappear.

4. Using it for simple “optional ownership”

If you just need to conditionally own a value, Option<T> is usually the correct abstraction.


Interactions with FFI

ManuallyDrop is especially useful when wrapping foreign resources that must be released by a specific C function rather than Rust’s allocator or destructor system.

For example, imagine a C library that returns an opaque handle and requires c_close(handle) to release it. A Rust wrapper might store the handle in ManuallyDrop to prevent accidental automatic cleanup and then call the foreign close function in Drop.

use std::mem::ManuallyDrop;

#[repr(C)]
struct CHandle {
    _private: [u8; 0],
}

extern "C" {
    fn c_open() -> *mut CHandle;
    fn c_close(handle: *mut CHandle);
}

pub struct Handle {
    raw: ManuallyDrop<*mut CHandle>,
}

impl Handle {
    pub fn new() -> Option<Self> {
        let ptr = unsafe { c_open() };
        if ptr.is_null() {
            None
        } else {
            Some(Self {
                raw: ManuallyDrop::new(ptr),
            })
        }
    }
}

impl Drop for Handle {
    fn drop(&mut self) {
        unsafe {
            c_close(*self.raw);
        }
    }
}

This pattern is common in systems programming, but it requires disciplined ownership rules. The wrapper should make it impossible for users to call the foreign close function twice or forget to call it.


When not to use ManuallyDrop

Avoid it in these cases:

  • ordinary application code with standard ownership needs
  • simple optional values
  • cases where Drop on the wrapper can express the cleanup logic
  • code that can be modeled with Option, Result, or RAII guards

A useful heuristic is this: if you are not also thinking carefully about initialization, partial moves, and destructor ordering, you probably do not need ManuallyDrop.


Design checklist

Before introducing ManuallyDrop, ask:

  1. Can this be expressed with Option<T> instead?
  2. Can Drop on the owning type handle cleanup cleanly?
  3. Is the destructor order truly significant?
  4. Can the unsafe behavior be hidden behind a safe abstraction?
  5. Have you documented exactly when the inner value is valid and when it is not?

If you cannot answer these clearly, the abstraction is likely too low-level for the problem.


Summary

std::mem::ManuallyDrop gives you precise control over destruction, but that control comes with responsibility. It is most valuable in advanced systems code where cleanup order, FFI boundaries, or custom ownership models matter. In everyday Rust, safer abstractions such as Option<T> and ordinary Drop implementations are usually better choices.

Use ManuallyDrop when you truly need it, keep it tightly encapsulated, and treat every manual destructor call as an unsafe boundary that deserves careful review.

Learn more with useful resources