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
Dropwith non-trivial destruction order
Use mem::forget when:
- Transferring ownership to external code (FFI)
- Intentionally leaking memory for
'staticlifetime - 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 leaksBox::into_raw()/Box::from_raw()for raw pointer conversionsRc::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.
