- Регистрация
- 1 Мар 2015
- Сообщения
- 1,481
- Баллы
- 155
? 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:
When to Use 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:?:<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:?:<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)
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:
And it’s especially critical for fields like:
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.
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:
This is ideal for situations where:
When you call v.push(x), here’s what actually happens:
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
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:
This is especially useful for Rust developers who want to:
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.
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
- 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
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:?:<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:?:<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:?:<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
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
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.
Why Vec<T> A Growable Heap Array ?“When you understand the memory, you control the performance.”
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
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)
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);
}
}
}
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
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.
Stay curious. Try building your own vector. And the next time you call .push(), know exactly how it works behind the scenes.“The more you understand how memory moves, the more power you have as a programmer.”