📦 What Is Box<T>
(A Smart Help Pointer)?
Box<T>
is a smart pointer that owns and stores its contents on the heap. It’s a core part of Rust’s ownership and memory management model, giving you fine control over data placement without sacrificing safety.
When you create a value with Box::new(x)
, Rust:
- Allocates memory on the heap
- Writes
x
into that memory - Returns a
Box<T>
that points to the heap
✅ When to Use Box<T>
- You need to store large data on the heap
- You want to build recursive types (like trees or linked lists)
- You’re working with trait objects and need dynamic dispatch (
Box<dyn Trait>
) - You want to transfer ownership safely across contexts
🛠️ A Simple Reimplementation of Box<T>
Want to know how Box<T>
works internally? Here's a simplified, educational version:
use std::alloc::{alloc, dealloc, Layout};
use std::ptr::NonNull;
pub struct MyBox<T> {
ptr: NonNull<T>,
}
impl<T> MyBox<T> {
pub fn new(value: T) -> Self {
let layout = Layout::new::<T>();
let ptr = unsafe { alloc(layout) as *mut T };
if ptr.is_null() {
panic!("Allocation failed");
}
unsafe {
ptr.write(value);
}
MyBox {
ptr: unsafe { NonNull::new_unchecked(ptr) },
}
}
pub fn get(&self) -> &T {
unsafe { self.ptr.as_ref() }
}
}
impl<T> Drop for MyBox<T> {
fn drop(&mut self) {
let layout = Layout::new::<T>();
unsafe {
std::ptr::drop_in_place(self.ptr.as_ptr());
dealloc(self.ptr.as_ptr() as *mut u8, layout);
}
}
}
🧠 How It Works (Step-by-Step)
-
Layout::new::<T>()
tells the allocator how much space to reserve -
alloc()
allocates raw heap memory -
ptr.write(value)
writes the value into that memory -
NonNull<T>
wraps the pointer for safety (non-null guarantee) -
Drop
is implemented to automatically clean up the memory when the box is dropped
🧩 Example Use
fn main() {
let boxed = MyBox::new(99);
println!("Heap value: {}", boxed.get());
}
🔍 Why This Matters for System Programming
Understanding how Box<T>
is built gives you insights into:
- Manual memory management (without garbage collection)
- Why Rust avoids double frees and leaks
- How to build your own allocators or smart pointers
And it’s especially critical for fields like:
- Blockchain development
- Operating system work
- Embedded systems
- Performance-critical network services
✅ Final Thoughts
Box<T>
looks simple — but behind it is an elegant, powerful system for safely managing heap memory. Learning how it works isn't just an academic exercise: it’s one of the first steps to mastering real Rust systems programming.
“When you understand the memory, you control the performance.”
Why Vec<T>
A Growable Heap Array ?
understanding how Vec<T>
works under the hood is crucial. It's more than just a dynamic array: it's an efficient, memory-safe, growable buffer backed by heap memory. In this post, we’ll walk through what Vec<T>
is, how it works, when to use it, and even how to reimplement a minimal version (MyVec<T>
) to deepen your mastery.
📦 What Is Vec<T>
?
Vec<T>
is a growable, contiguous vector type that stores elements on the heap. It keeps track of:
- A pointer to the buffer
- The current length (
len
) — how many elements are initialized - The capacity — how many elements the buffer can hold before needing to grow
This is ideal for situations where:
- You don’t know the size of your data at compile time
- You want fast, indexed access
- You may frequently add or remove elements
🧠 What Happens When You Call push()
?
When you call v.push(x)
, here’s what actually happens:
- If the length equals capacity, Rust grows the buffer (usually doubling the capacity)
- It calculates the address at
ptr.add(len)
— the next free slot - It writes the value using unsafe pointer write (
ptr.write(value)
) - It increments
len
pub fn push(&mut self, value: T) {
if self.len == self.capacity() {
self.grow();
}
unsafe {
let end = self.as_mut_ptr().add(self.len);
end.write(value);
}
self.len += 1;
}
Likewise, pop()
just moves the length back by 1 and reads the last element from memory:
pub fn pop(&mut self) -> Option<T> {
if self.len == 0 {
return None;
}
self.len -= 1;
unsafe {
let ptr = self.as_mut_ptr().add(self.len);
Some(ptr.read())
}
}
🔍 Memory Layout of Vec<T>
Buffer: [10][20][30][_][_][_]
Index : 0 1 2 capacity = 6
↑
len = 3
- All values are stored contiguously
- Insertions and deletions at the end are amortized O(1)
🛠️ Let’s Reimplement a Simplified MyVec<T>
Here’s a basic version of a vector that grows dynamically:
use std::alloc::{alloc, dealloc, realloc, Layout};
use std::ptr::NonNull;
use std::mem;
pub struct MyVec<T> {
ptr: NonNull<T>,
len: usize,
cap: usize,
}
impl<T> MyVec<T> {
pub fn new() -> Self {
MyVec {
ptr: NonNull::dangling(),
len: 0,
cap: 0,
}
}
pub fn push(&mut self, value: T) {
if self.len == self.cap {
self.grow();
}
unsafe {
let end = self.ptr.as_ptr().add(self.len);
end.write(value);
}
self.len += 1;
}
fn grow(&mut self) {
let (new_cap, new_layout) = if self.cap == 0 {
(4, Layout::array::<T>(4).unwrap())
} else {
let new_cap = self.cap * 2;
(new_cap, Layout::array::<T>(new_cap).unwrap())
};
let new_ptr = if self.cap == 0 {
unsafe { alloc(new_layout) as *mut T }
} else {
let old_layout = Layout::array::<T>(self.cap).unwrap();
unsafe { realloc(self.ptr.as_ptr() as *mut u8, old_layout, new_layout.size()) as *mut T }
};
self.ptr = NonNull::new(new_ptr).expect("allocation failed");
self.cap = new_cap;
}
pub fn pop(&mut self) -> Option<T> {
if self.len == 0 {
return None;
}
self.len -= 1;
unsafe { Some(self.ptr.as_ptr().add(self.len).read()) }
}
pub fn get(&self, index: usize) -> Option<&T> {
if index >= self.len {
return None;
}
unsafe { Some(&*self.ptr.as_ptr().add(index)) }
}
}
impl<T> Drop for MyVec<T> {
fn drop(&mut self) {
unsafe {
for i in 0..self.len {
std::ptr::drop_in_place(self.ptr.as_ptr().add(i));
}
let layout = Layout::array::<T>(self.cap).unwrap();
dealloc(self.ptr.as_ptr() as *mut u8, layout);
}
}
}
✨ Why Study the Internals of Vec<T>
?
Understanding how Vec<T>
works teaches you:
- How safe systems manage dynamic memory manually
- Why Rust avoids buffer overflows, dangling pointers, and double frees
- How real containers grow, shrink, and manage performance-critical memory
This is especially useful for Rust developers who want to:
- Build high-performance tools
- Write custom memory allocators or data structures
- Work in blockchain, embedded, OS, or graphics domains
🧠 Final Thoughts
By understanding Vec<T>
, you’re not just using Rust — you’re learning how it thinks. You're seeing the boundaries between safe code and unsafe code — and how Rust lets you cross those lines carefully, with purpose.
“The more you understand how memory moves, the more power you have as a programmer.”
Stay curious. Try building your own vector. And the next time you call .push()
, know exactly how it works behind the scenes.
Top comments (0)