Loading page…
Rust walkthroughs
Loading page…
ManuallyDrop is a wrapper that prevents Rust from automatically dropping the inner value. It's useful when you need manual control over when and how a value is destroyed, such as when working with raw pointers, FFI, or implementing custom allocation strategies.
Key characteristics:
Tunsafe required — Dropping requires calling unsafe methodsdrop() or take()When to use ManuallyDrop:
Warning: Forgetting to manually drop a ManuallyDrop value causes memory leaks!
use std::mem::ManuallyDrop;
fn main() {
// Create a ManuallyDrop wrapper
let mut value = ManuallyDrop::new(String::from("Hello, World!"));
// Access the inner value normally
println!("Value: {}", *value);
// The String will NOT be dropped when `value` goes out of scope!
// This would leak memory if we don't drop it manually
// Manually drop the value
unsafe {
ManuallyDrop::drop(&mut value);
}
println!("Value has been manually dropped");
}use std::mem::ManuallyDrop;
struct ImportantResource {
data: String,
}
impl Drop for ImportantResource {
fn drop(&mut self) {
println!("Dropping: {}", self.data);
}
}
fn main() {
{
// Normal drop
let normal = ImportantResource { data: String::from("normal") };
// Drop happens here automatically
}
println!("---");
{
// ManuallyDrop prevents automatic drop
let manual = ManuallyDrop::new(ImportantResource { data: String::from("manual") });
// No drop happens!
println!("Exiting scope...");
}
println!("(No drop message above - memory leaked!)");
// Proper usage:
let mut manual = ManuallyDrop::new(ImportantResource { data: String::from("proper") });
unsafe {
ManuallyDrop::drop(&mut manual);
}
}use std::mem::ManuallyDrop;
fn main() {
let mut manual = ManuallyDrop::new(vec![1, 2, 3, 4, 5]);
// take() extracts the value without dropping it
let vec: Vec<i32> = unsafe { ManuallyDrop::take(&mut manual) };
println!("Extracted: {:?}", vec);
// The ManuallyDrop is now empty - no drop will occur
// The Vec will be dropped normally when `vec` goes out of scope
}use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::ptr::NonNull;
struct MyBox<T> {
ptr: NonNull<T>,
// We need to drop the allocation, but we also need to drop T
// ManuallyDrop lets us control this
}
impl<T> MyBox<T> {
fn new(value: T) -> Self {
let boxed = Box::new(value);
let ptr = NonNull::new(Box::into_raw(boxed)).unwrap();
Self { ptr }
}
}
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) {
unsafe {
// First drop the value
std::ptr::drop_in_place(self.ptr.as_ptr());
// Then deallocate the memory
let _ = Box::from_raw(self.ptr.as_ptr());
}
}
}
fn main() {
let my_box = MyBox::new(String::from("Hello"));
println!("Value: {}", *my_box);
// Dropped properly when my_box goes out of scope
}use std::mem::ManuallyDrop;
// Unions require ManuallyDrop for non-Copy types
#[derive(Debug)]
enum Value {
Integer(i64),
Float(f64),
}
union SafeUnion {
int: i64,
float: f64,
// For non-Copy types, you would use:
// string: ManuallyDrop<String>,
}
fn main() {
let mut u = SafeUnion { int: 42 };
unsafe {
println!("Integer: {}", u.int);
// Write a float
u.float = 3.14;
println!("Float: {}", u.float);
}
}use std::mem::ManuallyDrop;
union StringOrInt {
string: ManuallyDrop<String>,
int: i64,
}
impl StringOrInt {
fn from_string(s: String) -> Self {
Self {
string: ManuallyDrop::new(s),
}
}
fn from_int(i: i64) -> Self {
Self { int: i }
}
fn as_string(&self) -> Option<&String> {
// In real code, you'd need a tag to know which variant
// This is simplified for demonstration
unsafe { Some(&self.string) }
}
}
impl Drop for StringOrInt {
fn drop(&mut self) {
// In real code, check a tag to know which variant to drop
// This example always drops the string variant (incorrect!)
// This is why unions need careful handling
}
}
fn main() {
let string_val = StringOrInt::from_string(String::from("hello"));
unsafe {
println!("String: {}", *string_val.string);
}
// Proper cleanup needed!
}use std::mem::ManuallyDrop;
struct MyVec<T> {
ptr: *mut T,
len: usize,
capacity: usize,
}
impl<T> MyVec<T> {
fn new() -> Self {
Self {
ptr: std::ptr::null_mut(),
len: 0,
capacity: 0,
}
}
fn push(&mut self, value: T) {
if self.len == self.capacity {
self.grow();
}
unsafe {
std::ptr::write(self.ptr.add(self.len), value);
}
self.len += 1;
}
fn grow(&mut self) {
let new_capacity = if self.capacity == 0 { 4 } else { self.capacity * 2 };
let new_layout = std::alloc::Layout::array::<T>(new_capacity).unwrap();
let new_ptr = if self.capacity == 0 {
unsafe { std::alloc::alloc(new_layout) as *mut T }
} else {
let old_layout = std::alloc::Layout::array::<T>(self.capacity).unwrap();
unsafe {
std::alloc::realloc(self.ptr as *mut u8, old_layout, new_layout.size()) as *mut T
}
};
self.ptr = new_ptr;
self.capacity = new_capacity;
}
fn len(&self) -> usize {
self.len
}
}
impl<T> Drop for MyVec<T> {
fn drop(&mut self) {
if self.capacity > 0 {
// Drop all elements
for i in 0..self.len {
unsafe {
std::ptr::drop_in_place(self.ptr.add(i));
}
}
// Deallocate
let layout = std::alloc::Layout::array::<T>(self.capacity).unwrap();
unsafe {
std::alloc::dealloc(self.ptr as *mut u8, layout);
}
}
}
}
fn main() {
let mut vec = MyVec::new();
vec.push(1);
vec.push(2);
vec.push(3);
println!("Length: {}", vec.len());
}use std::mem::ManuallyDrop;
use std::ptr;
struct LazyBuffer {
// Start uninitialized, then initialize on first use
data: ManuallyDrop<Vec<u8>>,
initialized: bool,
}
impl LazyBuffer {
fn new() -> Self {
Self {
// Safe because we check initialized before use
data: unsafe { std::mem::uninitialized() },
initialized: false,
}
}
fn get(&mut self) -> &Vec<u8> {
if !self.initialized {
// Initialize on first access
self.data = ManuallyDrop::new(vec![0; 1024]);
self.initialized = true;
}
&self.data
}
}
impl Drop for LazyBuffer {
fn drop(&mut self) {
if self.initialized {
unsafe {
ManuallyDrop::drop(&mut self.data);
}
}
// If not initialized, don't drop - nothing to clean up
}
}
fn main() {
let mut buffer = LazyBuffer::new();
println!("Created but not initialized");
let data = buffer.get();
println!("Buffer size: {}", data.len());
}use std::mem::ManuallyDrop;
// Imagine a C function that takes ownership of data
// void c_take_ownership(void* data, size_t len);
struct OwnedData {
data: ManuallyDrop<Vec<u8>>,
}
impl OwnedData {
fn new(data: Vec<u8>) -> Self {
Self {
data: ManuallyDrop::new(data),
}
}
fn transfer_to_c(self) {
// Prevent Rust from dropping the data
let data = ManuallyDrop::into_inner(unsafe { std::ptr::read(&self.data) });
// Now transfer ownership to C
// C is responsible for freeing this memory
let ptr = data.as_ptr();
let len = data.len();
// Forget the Vec so Rust doesn't try to free it
std::mem::forget(data);
// Call C function (simulated)
println!("Transferring {} bytes at {:?} to C", len, ptr);
// In real code:
// unsafe { c_take_ownership(ptr as *mut void, len); }
}
}
fn main() {
let owned = OwnedData::new(vec![1, 2, 3, 4, 5]);
owned.transfer_to_c();
}use std::mem::ManuallyDrop;
struct Container {
data: ManuallyDrop<String>,
should_drop: bool,
}
impl Container {
fn new(s: String) -> Self {
Self {
data: ManuallyDrop::new(s),
should_drop: true,
}
}
fn dont_drop(mut self) {
self.should_drop = false;
// self won't drop the inner String
}
}
impl Drop for Container {
fn drop(&mut self) {
if self.should_drop {
unsafe {
ManuallyDrop::drop(&mut self.data);
}
}
}
}
fn main() {
let c1 = Container::new(String::from("will drop"));
// c1.data will be dropped when c1 goes out of scope
let c2 = Container::new(String::from("won't drop"));
c2.dont_drop();
// c2.data will NOT be dropped
println!("Containers handled");
}use std::mem::ManuallyDrop;
fn main() {
let mut manual = ManuallyDrop::new(String::from("test"));
// Deref to access inner value
println!("Value: {}", *manual);
// DerefMut to modify
manual.push_str(" modified");
println!("Modified: {}", *manual);
// Check size
println!("Size of ManuallyDrop<String>: {}", std::mem::size_of_val(&manual));
println!("Size of String: {}", std::mem::size_of::<String>());
// Same size!
// into_inner consumes and returns the value
let string = ManuallyDrop::into_inner(manual);
println!("Extracted: {}", string);
// string will be dropped normally
// take extracts without consuming ManuallyDrop
let mut manual2 = ManuallyDrop::new(vec![1, 2, 3]);
let vec = unsafe { ManuallyDrop::take(&mut manual2) };
println!("Taken: {:?}", vec);
}use std::mem::ManuallyDrop;
struct Slot<T> {
data: ManuallyDrop<T>,
occupied: bool,
}
impl<T> Slot<T> {
fn new_uninit() -> Self {
Self {
data: unsafe { std::mem::zeroed() },
occupied: false,
}
}
fn is_occupied(&self) -> bool {
self.occupied
}
fn insert(&mut self, value: T) -> Result<(), T> {
if self.occupied {
return Err(value);
}
self.data = ManuallyDrop::new(value);
self.occupied = true;
Ok(())
}
fn remove(&mut self) -> Option<T> {
if !self.occupied {
return None;
}
self.occupied = false;
// Take the value out
Some(unsafe { ManuallyDrop::take(&mut self.data) })
}
}
impl<T> Drop for Slot<T> {
fn drop(&mut self) {
if self.occupied {
unsafe {
ManuallyDrop::drop(&mut self.data);
}
}
}
}
fn main() {
let mut slot: Slot<String> = Slot::new_uninit();
println!("Occupied: {}", slot.is_occupied());
slot.insert(String::from("hello")).unwrap();
println!("Occupied: {}", slot.is_occupied());
let value = slot.remove().unwrap();
println!("Removed: {}", value);
println!("Occupied: {}", slot.is_occupied());
}ManuallyDrop Methods:
| Method | Description | Safety |
|--------|-------------|--------|
| new(value) | Create new ManuallyDrop | Safe |
| into_inner(this) | Extract value, consuming wrapper | Safe |
| take(slot) | Extract value without consuming | Unsafe |
| drop(slot) | Manually drop the inner value | Unsafe |
| deref / deref_mut | Access inner value | Safe |
Comparison with Related Types:
| Type | Auto Drop | Use Case |
|------|-----------|----------|
| T | Yes | Normal values |
| ManuallyDrop<T> | No | Manual drop control |
| MaybeUninit<T> | No | Uninitialized memory |
| mem::forget() | Prevents drop | One-time forget |
When to Use ManuallyDrop:
| Scenario | Appropriate? | |----------|-------------| | Custom smart pointers | ✅ Yes | | Vec/Collection internals | ✅ Yes | | FFI ownership transfer | ✅ Yes | | Unions with non-Copy types | ✅ Yes | | Avoiding double-drop | ✅ Yes | | Simple resource cleanup | ❌ Use normal Drop | | Thread-safe shared state | ❌ Use Arc/Mutex |
Key Points:
ManuallyDrop<T> prevents automatic DropT (zero overhead)drop() and take() are unsafe methodsinto_inner() to safely extract and transfer ownershipMaybeUninit for uninitialized + manual drop control