Loading pageâŚ
Rust walkthroughs
Loading pageâŚ
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.
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.
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 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.
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:
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); }
}
}
}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);
}
}
}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:
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]);
}
}
}
}
}mem::forget is appropriate when you explicitly want to leak memory or resources:
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
}IntoRaw Patternsuse 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);
}
}
}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);
}| 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 |
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!
}Use ManuallyDrop when:
Drop with non-trivial destruction orderUse mem::forget when:
'static lifetimePrefer 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 typesManuallyDrop 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.