Loading page…
Rust walkthroughs
Loading page…
mem::forget(value) takes ownership of a value and "forgets" about it without running its destructor. The value's memory is leaked, but this is sometimes necessary for FFI, transferring ownership, or implementing certain patterns safely.
Key concepts:
Drop::drop is never calledforget is safe! Leaking is not unsafe in RustWhen to use mem::forget:
ManuallyDrop-like semantics inlineImportant considerations:
ManuallyDrop when you need more controlstd::mem::ManuallyDrop for better ergonomicsuse std::mem;
struct Resource {
name: String,
}
impl Drop for Resource {
fn drop(&mut self) {
println!("Dropping resource: {}", self.name);
}
}
fn main() {
// Normal drop
{
let r = Resource { name: String::from("normal") };
// Drop is called at end of scope
}
println!("---");
// Forgotten - no drop
{
let r = Resource { name: String::from("forgotten") };
mem::forget(r);
// Drop is NOT called
println!("After forget");
}
println!("End of scope");
}use std::mem;
use std::ffi::CString;
use std::os::raw::c_char;
// Simulated FFI function that takes ownership
extern "C" {
fn free(ptr: *mut c_char);
}
fn transfer_to_ffi(s: String) {
let c_string = CString::new(s).unwrap();
let ptr = c_string.into_raw();
// Transfer ownership to external code
// Don't run CString's destructor - external code will free it
mem::forget(c_string);
// ptr is now owned by external code
// In real FFI: external code will call free(ptr)
// For this example, we'll just leak it
println!("Transferred pointer: {:?}", ptr);
}
fn main() {
transfer_to_ffi(String::from("Hello FFI"));
println!("Transfer complete");
}use std::mem;
use std::fs::File;
use std::io::{self, Write};
struct AtomicWriter {
file: Option<File>,
temp_path: String,
final_path: String,
}
impl AtomicWriter {
fn new(temp_path: &str, final_path: &str) -> io::Result<Self> {
Ok(Self {
file: Some(File::create(temp_path)?),
temp_path: temp_path.to_string(),
final_path: final_path.to_string(),
})
}
fn write(&mut self, data: &[u8]) -> io::Result<()> {
self.file.as_mut().unwrap().write_all(data)
}
fn commit(mut self) -> io::Result<()> {
// Close the file first
self.file = None;
// Rename temp to final
std::fs::rename(&self.temp_path, &self.final_path)?;
// Success! Don't delete the temp file in drop
mem::forget(self);
Ok(())
}
}
impl Drop for AtomicWriter {
fn drop(&mut self) {
// Only runs if commit wasn't called
// Clean up the temp file
let _ = std::fs::remove_file(&self.temp_path);
println!("Cleaned up temp file: {}", self.temp_path);
}
}
fn main() -> io::Result<()> {
let mut writer = AtomicWriter::new("temp.txt", "final.txt")?;
writer.write(b"Hello, World!")?;
// Commit - temp file is NOT deleted
writer.commit()?;
println!("Commit successful");
Ok(())
}use std::mem;
struct Handle {
id: u32,
resource: *mut std::ffi::c_void,
}
impl Handle {
fn new(id: u32) -> Self {
Self {
id,
resource: std::ptr::null_mut(),
}
}
// Transfer ownership to external code
fn into_raw(self) -> *mut std::ffi::c_void {
let ptr = self.resource;
mem::forget(self); // Don't run Drop
ptr
}
}
impl Drop for Handle {
fn drop(&mut self) {
println!("Dropping handle {}", self.id);
// Would normally free self.resource
}
}
fn main() {
let handle = Handle::new(42);
// Transfer ownership
let ptr = handle.into_raw();
println!("Transferred: {:?}", ptr);
// handle's Drop was not called
}
struct Session {
token: String,
}
impl Session {
fn new() -> Self {
Self {
token: String::from("secret_token"),
}
}
// Must explicitly close - can't just drop
fn close(self) {
println!("Closing session with token: {}", self.token);
// Normal cleanup
// Drop will NOT run because we consume self
mem::forget(self);
}
// Alternative: close with error
fn close_with_error(self, reason: &str) {
println!("Error closing session: {}", reason);
mem::forget(self);
}
}
impl Drop for Session {
fn drop(&mut self) {
panic!("Session must be explicitly closed with .close()");
}
}
fn main() {
let session = Session::new();
session.close();
// Safe - Drop was not called
// This would panic:
// let session2 = Session::new();
// session2 dropped without calling close()
}use std::mem;
use std::ptr::NonNull;
struct OwnedBuffer {
ptr: NonNull<u8>,
len: usize,
}
impl OwnedBuffer {
fn new(len: usize) -> Self {
let layout = std::alloc::Layout::array::<u8>(len).unwrap();
let ptr = unsafe { std::alloc::alloc(layout) };
let ptr = NonNull::new(ptr).expect("allocation failed");
Self { ptr, len }
}
fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
fn as_slice_mut(&mut self) -> &mut [u8] {
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
}
// Transfer ownership - don't free on our side
fn leak(self) -> (*mut u8, usize) {
let (ptr, len) = (self.ptr.as_ptr(), self.len);
mem::forget(self);
(ptr, len)
}
// Reclaim a leaked buffer
fn from_leaked(ptr: *mut u8, len: usize) -> Self {
Self {
ptr: unsafe { NonNull::new_unchecked(ptr) },
len,
}
}
}
impl Drop for OwnedBuffer {
fn drop(&mut self) {
let layout = std::alloc::Layout::array::<u8>(self.len).unwrap();
unsafe {
std::alloc::dealloc(self.ptr.as_ptr(), layout);
}
println!("Freed buffer of {} bytes", self.len);
}
}
fn main() {
let buffer = OwnedBuffer::new(1024);
// Transfer ownership somewhere else
let (ptr, len) = buffer.leak();
println!("Leaked buffer: ptr={:?}, len={}", ptr, len);
// Can reclaim later
let reclaimed = OwnedBuffer::from_leaked(ptr, len);
println!("Reclaimed buffer of {} bytes", reclaimed.len);
// reclaimed will be dropped properly
}use std::mem::{self, ManuallyDrop};
struct Data {
value: String,
}
impl Drop for Data {
fn drop(&mut self) {
println!("Dropping: {}", self.value);
}
}
fn main() {
// mem::forget - consumes the value
let d1 = Data { value: String::from("forget") };
mem::forget(d1);
// d1 is gone, can't access it
// ManuallyDrop - keeps the value accessible
let mut d2 = ManuallyDrop::new(Data { value: String::from("manually_drop") });
println!("Value: {}", d2.value);
// Can still use it
d2.value.push_str(" modified");
println!("Modified: {}", d2.value);
// Drop when ready
unsafe {
ManuallyDrop::drop(&mut d2);
}
}use std::mem;
use std::ffi::c_void;
// Simulated FFI callback system
static mut CALLBACK_DATA: *mut c_void = std::ptr::null_mut();
extern "C" fn simulate_register_callback(data: *mut c_void) {
unsafe {
CALLBACK_DATA = data;
}
}
struct CallbackContext {
name: String,
data: Vec<u8>,
}
impl CallbackContext {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
data: Vec::new(),
}
}
fn register(self) {
let ptr = Box::into_raw(Box::new(self)) as *mut c_void;
// Register with FFI - external code now owns the data
simulate_register_callback(ptr);
// Don't drop - FFI owns it now
// (Box::into_raw already prevents drop, but showing the pattern)
mem::forget(ptr); // Actually not needed here - into_raw handles it
}
unsafe fn unregister() {
if !CALLBACK_DATA.is_null() {
let _box = Box::from_raw(CALLBACK_DATA as *mut Self);
// Will be dropped properly now
CALLBACK_DATA = std::ptr::null_mut();
}
}
}
impl Drop for CallbackContext {
fn drop(&mut self) {
println!("Dropping context: {}", self.name);
}
}
fn main() {
let ctx = CallbackContext::new("my_callback");
ctx.register();
println!("Callback registered");
// Later, unregister to clean up
unsafe {
CallbackContext::unregister();
}
}use std::mem;
use std::thread;
struct Scope<'a> {
// Would normally hold references to stack data
_marker: std::marker::PhantomData<&'a ()>,
}
impl<'a> Scope<'a> {
fn spawn<F>(&self, f: F)
where
F: FnOnce() + 'a,
{
// In a real implementation, this would spawn a thread
// that must complete before 'a ends
f();
}
}
fn scope<'a, F, R>(f: F) -> R
where
F: FnOnce(&Scope<'a>) -> R,
{
let scope = Scope { _marker: std::marker::PhantomData };
let result = f(&scope);
// Ensure all scoped threads complete here
// (simplified - real scoped_threadpool is more complex)
result
}
fn main() {
let data = vec![1, 2, 3, 4, 5];
scope(|s| {
s.spawn(|| {
println!("Data: {:?}", data);
});
});
}use std::mem;
use std::sync::atomic::{AtomicUsize, Ordering};
static LEAK_COUNT: AtomicUsize = AtomicUsize::new(0);
struct TrackedLeak<T> {
value: T,
}
impl<T> TrackedLeak<T> {
fn new(value: T) -> Self {
Self { value }
}
fn leak(self) -> &'static T {
LEAK_COUNT.fetch_add(1, Ordering::SeqCst);
let ptr = Box::into_raw(Box::new(self.value));
mem::forget(self); // Don't drop self
unsafe { &*ptr }
}
}
impl<T> Drop for TrackedLeak<T> {
fn drop(&mut self) {
// Normal drop - no leak
LEAK_COUNT.fetch_sub(1, Ordering::SeqCst);
}
}
fn main() {
let leaked1 = TrackedLeak::new(42i32).leak();
let leaked2 = TrackedLeak::new(String::from("leaked")).leak();
println!("Leaked int: {}", leaked1);
println!("Leaked string: {}", leaked2);
println!("Leak count: {}", LEAK_COUNT.load(Ordering::SeqCst));
}use std::mem;
fn main() {
// DON'T use forget when you want to explicitly drop
// Use drop() instead:
let s1 = String::from("hello");
drop(s1); // This runs the destructor
// DON'T use forget when ManuallyDrop is more appropriate
// If you need to access the value after "forgetting",
// use ManuallyDrop:
use std::mem::ManuallyDrop;
let mut s2 = ManuallyDrop::new(String::from("world"));
// Can still access
println!("{}", *s2);
unsafe { ManuallyDrop::drop(&mut s2); }
// DON'T use forget for temporary optimization
// Leaking memory is usually not the right solution
//
// Instead, consider:
// - Reusing allocations
// - Using arenas
// - Pooling resources
}mem::forget Characteristics:
| Property | Description | |----------|-------------| | Safety | Safe (leaking is not UB) | | Destructor | Not called | | Memory | Leaked (never reclaimed) | | Ownership | Consumed |
mem::forget vs Alternatives:
| Approach | Use Case | Destructor |
|----------|----------|------------|
| mem::forget(v) | Transfer ownership, consume value | Not called |
| ManuallyDrop<T> | Need to access value after | Manual control |
| drop(v) | Explicitly run destructor | Called |
| Box::into_raw(b) | Transfer Box ownership | Not called |
| std::mem::take() | Replace with default | Old value dropped |
When to Use mem::forget:
| Scenario | Appropriate? |
|----------|-------------|
| FFI ownership transfer | ✅ Yes |
| Preventing drop on success path | ✅ Yes |
| Implementing affine types | ✅ Yes |
| into_raw() patterns | ✅ Yes |
| Normal cleanup | ❌ Use drop() |
| Need to access value later | ❌ Use ManuallyDrop |
| Performance optimization | ⚠️ Rarely needed |
Key Points:
mem::forget prevents the destructor from runningManuallyDrop is often more ergonomic for controlled destructionBox::into_raw for heap-allocated valuesinto_raw() methods