---
title: How to Use std::mem for Memory Operations
summary: Use std::mem functions like swap, replace, and size_of to manage memory layout and value exchange efficiently in Rust.
lang: en
slug: how-to-use-stdmem-for-memory-operations
published: 2026-04-17T14:12:14+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-use-stdmem-for-memory-operations/
---

# How to Use std::mem for Memory Operations



## When you need to move data without moving data

You are building a request handler. The handler struct holds a buffer of incoming bytes. When the request finishes, you want to hand that buffer to the response logic. You try to move the buffer out of the handler, but the handler needs to stay alive for the next request. The compiler blocks you with E0507 (cannot move out of borrowed content). You can't move a field out of a struct unless the whole struct is dropped or you use a specific trick.

You also have two large arrays you need to exchange. Copying the bytes is slow. You need to swap the pointers.

`std::mem` is the toolbox for these memory-level maneuvers. It lets you manipulate values in place, check sizes, control lifetimes, and handle uninitialized memory without fighting the borrow checker or sacrificing performance.

## The mechanic's toolkit

Think of your program as a warehouse. Most of the time, you are moving crates around: taking a crate, opening it, putting it back. That's normal logic. `std::mem` is the mechanic's toolkit for the warehouse structure itself. You use it to swap the labels on two crates without touching the contents. You use it to swap a full crate for an empty box and keep the old crate. You use it to check the blueprint to see how much shelf space a crate type takes. You use it to tell the warehouse manager to ignore a crate so it doesn't get recycled.

These operations are low-level. They touch the memory layout directly. They are safe to use, but they require understanding what the compiler is doing under the hood.

## Swap and replace: exchanging values in place

The most common operations are `swap` and `replace`. They let you exchange values without copying data.

```rust
use std::mem;

/// Swaps two values and replaces a value with a placeholder.
fn main() {
    let mut a = String::from("hello");
    let mut b = String::from("world");

    // Swap exchanges the values in place.
    // For String, this swaps the pointer, length, and capacity.
    // No characters are copied. This is O(1).
    mem::swap(&mut a, &mut b);
    println!("a: {}, b: {}", a, b);

    // Replace takes a mutable reference, puts a new value,
    // and returns the old value.
    // This is useful when you need the old value but the
    // slot must remain valid.
    let old_a = mem::replace(&mut a, String::new());
    println!("old_a: {}, a: {}", old_a, a);
}
```

`swap` requires two mutable references. It exchanges the values. For types like `String`, `Vec`, or `Box`, the value is just a pointer, length, and capacity. Swapping those three words is instant. The heap data stays where it is. The pointers just change targets.

`replace` is more flexible. It takes a mutable reference to a slot, writes a new value into that slot, and returns the value that was there. This is the key to extracting data from a struct without destroying the struct.

Don't copy bytes. Swap pointers.

## The take idiom: extracting from Option

A common pattern is extracting a value from an `Option` or `Vec` inside a struct. You want the value, but you want the struct to stay valid. `mem::replace` works, but there is a cleaner convention.

```rust
struct Request {
    id: u32,
    body: Option<Vec<u8>>,
}

/// Extracts the body from the request, leaving None behind.
fn process(mut req: Request) {
    // mem::take replaces the value with Default::default().
    // For Option, the default is None.
    // This extracts the Vec without moving out of req.
    let body = mem::take(&mut req.body);

    // req.body is now None.
    // req is still valid and can be reused.
    println!("Processing request {} with body size {}", req.id, body.as_ref().map_or(0, |v| v.len()));
}
```

`mem::take` is a function that calls `replace` with `Default::default()`. For `Option<T>`, the default is `None`. For `Vec<T>`, the default is an empty vector. The community convention is to use `take` when the default value is the natural placeholder. It signals intent: you are taking the value and leaving the slot in its default state.

Using `take` is clearer than `replace` with `None`. Readers recognize the pattern immediately.

Extract without destroying the container.

## Checking sizes: layout and heap

`std::mem::size_of` tells you how many bytes a type takes in memory. This is a compile-time constant. The compiler calculates it based on the type definition.

```rust
use std::mem;

fn main() {
    // size_of is a compile-time constant.
    // String is a fat pointer: ptr, len, cap.
    // On 64-bit systems, that's 8 + 8 + 8 = 24 bytes.
    let string_size = mem::size_of::<String>();
    println!("Size of String: {} bytes", string_size);

    // Vec is the same layout as String.
    let vec_size = mem::size_of::<Vec<u8>>();
    println!("Size of Vec<u8>: {} bytes", vec_size);

    // i32 is 4 bytes.
    let int_size = mem::size_of::<i32>();
    println!("Size of i32: {} bytes", int_size);
}
```

`size_of` only measures the type itself. It does not include heap data. A `String` with a million characters is still 24 bytes according to `size_of`. The characters live on the heap. `size_of` measures the handle, not the payload.

For slices and dynamically sized types, the size is not known at compile time. You need `size_of_val`.

```rust
use std::mem;

fn main() {
    let slice: &[u8] = &[1, 2, 3, 4, 5];

    // size_of::<[u8]>() fails because the size is unknown.
    // size_of_val measures the actual data behind the reference.
    let data_size = mem::size_of_val(slice);
    println!("Slice data size: {} bytes", data_size);

    // A slice reference itself is 16 bytes: ptr + len.
    let ref_size = mem::size_of::<&[u8]>();
    println!("Slice reference size: {} bytes", ref_size);
}
```

`size_of_val` takes a reference and returns the size of the value it points to. This is essential for working with slices, strings, and trait objects.

Know your layout. Heap data doesn't count.

## Controlling lifetimes: drop and forget

Rust drops values automatically when they go out of scope. Sometimes you need to control that timing. `drop` lets you drop a value early.

```rust
use std::mem;

fn main() {
    let mut vec = vec![1, 2, 3];

    // Create a borrow.
    let borrow = &vec[0];

    // This would fail: cannot borrow vec as mutable because
    // it is also borrowed as immutable.
    // vec.push(4);

    // drop ends the borrow early.
    mem::drop(borrow);

    // Now the borrow is gone. We can mutate vec.
    vec.push(4);
    println!("{:?}", vec);
}
```

`drop` is a function that takes ownership and drops the value. It's often used to end a borrow when the borrow is no longer needed but the variable is still in scope.

`mem::forget` does the opposite. It prevents a value from being dropped. This leaks memory.

```rust
use std::mem;

fn main() {
    let data = String::from("leaked");

    // forget prevents the String from being dropped.
    // The heap memory is leaked.
    mem::forget(data);

    // data is gone. We can't use it.
    // The memory is lost until the program exits.
}
```

`forget` is dangerous. It's used in unsafe abstractions where you take ownership of memory and manage it manually. For most code, avoid `forget`. If you need to suppress a drop, use `ManuallyDrop` instead. It makes the intent explicit and keeps the value accessible.

Trust the borrow checker. It usually has a point. If you need `forget`, you are likely building a safe abstraction.

## Uninitialized memory: MaybeUninit

Sometimes you need to allocate memory without initializing it. This happens in performance-critical code or when building data structures. `std::mem::MaybeUninit` is the safe way to handle uninitialized memory.

```rust
use std::mem::MaybeUninit;

/// Writes to uninitialized memory safely.
fn main() {
    // Allocate space for a String without initializing it.
    // This avoids running the String constructor.
    let mut slot = MaybeUninit::<String>::uninit();

    // write places a value into the slot without dropping
    // the old value. There is no old value, so this is safe.
    // This is useful when you want to construct a value
    // in place.
    unsafe {
        // SAFETY: slot is uninitialized. write does not
        // drop the old value. We are writing a valid String.
        slot.write(String::from("hello"));
    }

    // assume_init tells the compiler the value is initialized.
    // This is unsafe because the compiler trusts you.
    // If the value is not initialized, this is undefined behavior.
    let s = unsafe {
        // SAFETY: We wrote a String into slot above.
        // The value is fully initialized.
        slot.assume_init()
    };

    println!("{}", s);
}
```

`MaybeUninit` wraps a value that might not be initialized. `write` places a value without dropping the old one. `assume_init` extracts the value. Both are unsafe because you must ensure the memory is valid.

`MaybeUninit` replaces `transmute` in most cases. It's the standard way to handle uninitialized memory in Rust.

Transmute is a lie to the compiler. If you can't prove the lie is safe, don't tell it.

## Pitfalls and compiler errors

`std::mem` functions are powerful, but they have traps.

If you try to move a field out of a struct, the compiler rejects you with E0507 (cannot move out of borrowed content). This happens when you try to extract a non-Copy value from a struct that needs to stay alive. Use `mem::replace` or `mem::take` to extract the value and leave a placeholder.

If you call `size_of` on a slice or trait object, the compiler rejects you with E0071 (cannot apply unary operator). The size is not known at compile time. Use `size_of_val` instead.

If you use `transmute`, you risk undefined behavior. `transmute` tells the compiler to treat one type as another. If the layouts don't match or the bit patterns are invalid, the program crashes or corrupts memory. Avoid `transmute`. Use `MaybeUninit`, `ptr::write`, or safe conversions instead.

If you call `assume_init` on uninitialized memory, you get undefined behavior. The compiler assumes the value is valid. If it's not, anything can happen. Always ensure the value is initialized before calling `assume_init`.

Don't fight the compiler here. Reach for `MaybeUninit`.

## Decision: when to use std::mem functions

Use `mem::swap` when you need to exchange two values without copying data. Use `mem::replace` when you need to extract a value from a slot but must leave a placeholder to keep the struct valid. Use `mem::take` when extracting from an `Option` or `Vec` and the default value is the natural placeholder. Use `mem::size_of` when you need the layout size of a type at compile time. Use `mem::size_of_val` when dealing with slices or dynamically sized types where the size is only known at runtime. Use `drop` when you need to end a borrow early or force cleanup of a value. Use `MaybeUninit` when you need to allocate memory without initializing it or write a value without dropping the old one. Avoid `mem::transmute` unless you are implementing a safe abstraction and have audited the layout. Reach for `MaybeUninit` or `ptr::write` instead.

Counter-intuitive but true: the more you use `unsafe`, the harder the rest of your code becomes to reason about.

## Where to go next

- [How to Use std::hash for Hashing Custom Types](/en/how-to-use-stdhash-for-hashing-custom-types/)
- [How to Build a Custom Parser in Rust](/en/how-to-build-a-custom-parser-in-rust/)
- [How to Build a TCP Client in Rust](/en/how-to-build-a-tcp-client-in-rust/)