Smart Pointers

Heap Allocation with Box

Heap allocation with Box<T> transfers values from fixed-size thread stack frames into the process heap, reducing stack frame pressure, making ownership transfers constant-time (8-byte pointer moves), and providing owned storage for unsized slices (Box<[T]>, Box<str>) and trait objects (Box<dyn Trait>).

Avoiding Expensive Stack Copies on Move

Moving a value in Rust performs a bitwise shallow copy (memcpy) of its stack representation. For a 24-byte Vec<T> or String, moving is already cheap because only the 24-byte header is copied. For a large fixed-size array ([u8; 65_536]) or a large struct, moving copies all 65,536 bytes on every transfer and risks overflowing the thread stack. Placing the value inside a Box<T> reduces every subsequent move to copying an 8-byte pointer.

struct AudioFrame {
    samples: [f32; 4096],
}

fn process_frame(mut frame: Box<AudioFrame>) -> Box<AudioFrame> {
    frame.samples[0] = 1.0;
    frame.samples[4095] = -1.0;
    frame
}

fn main() {
    let frame = Box::new(AudioFrame {
        samples: [0.0; 4096],
    });

    println!(
        "stack handle size: {} bytes, heap payload size: {} bytes",
        size_of_val(&frame),
        size_of_val(&*frame)
    );

    let processed = process_frame(frame);
    println!(
        "endpoints: ({}, {})",
        processed.samples[0], processed.samples[4095]
    );
}
stack handle size: 8 bytes, heap payload size: 16384 bytes
endpoints: (1, -1)

Owned Dynamically Sized Types (Box<[T]>, Box<str>, Box<dyn Trait>)

Because dynamically sized types ([T], str, dyn Trait) do not have a compile-time known size, they cannot be owned directly as local variables. Box acts as a two-word fat pointer (16 bytes on 64-bit targets) that owns the heap-allocated DST:

Boxed DST Stack Size (64-bit) How to Construct Advantage Over Growable Counterpart
Box<[T]> 16 bytes (ptr, len) vec.into_boxed_slice() or Box::from(&slice[..]) Saves 8 bytes (cap word) compared to Vec<T> (24 bytes) and forbids accidental reallocation
Box<str> 16 bytes (ptr, len) string.into_boxed_str() or Box::from("text") Saves 8 bytes (cap word) compared to String (24 bytes) for immutable heap strings
Box<dyn Trait> 16 bytes (ptr, vtable) Box::new(concrete_value) coerced to Box<dyn Trait> Allows heterogeneous collections (Vec<Box<dyn Trait>>) with single ownership
trait Job {
    fn execute(&self) -> String;
}

struct EmailJob {
    recipient: Box<str>,
}

impl Job for EmailJob {
    fn execute(&self) -> String {
        format!("Sent email to {}", self.recipient)
    }
}

struct BackupJob {
    shards: Box<[u16]>,
}

impl Job for BackupJob {
    fn execute(&self) -> String {
        format!("Backed up {} shards: {:?}", self.shards.len(), self.shards)
    }
}

fn main() {
    let queue: Vec<Box<dyn Job>> = vec![
        Box::new(EmailJob {
            recipient: String::from("ops@example.com").into_boxed_str(),
        }),
        Box::new(BackupJob {
            shards: vec![1, 2, 3].into_boxed_slice(),
        }),
    ];

    for job in &queue {
        println!("{}", job.execute());
    }
}
Sent email to ops@example.com
Backed up 3 shards: [1, 2, 3]

Runtime-Static Configuration with Box::leak

When configuration data is loaded at runtime (from environment variables or a file) and needs to be shared as a 'static reference across threads for the entire lifetime of the process without reference-counting overhead, Box::leak converts Box<T> into &'static mut T (which coerces to &'static T).

#[derive(Debug)]
struct AppConfig {
    host: String,
    port: u16,
}

fn init_config() -> &'static AppConfig {
    let cfg = Box::new(AppConfig {
        host: String::from("127.0.0.1"),
        port: 8080,
    });
    Box::leak(cfg)
}

fn main() {
    let static_cfg: &'static AppConfig = init_config();
    println!("listening on {}:{}", static_cfg.host, static_cfg.port);
}
listening on 127.0.0.1:8080

Zero-Sized Allocations in Box<T>

When T is a zero-sized type (size_of::<T>() == 0, such as Box::new(()) or Box::<[u8]>::from(&[][..])), Box does not call the global heap allocator. Instead, it stores a dangling, properly aligned non-null pointer (NonNull::dangling()) and performs a no-op on Drop.