Skip to content

Memory Layout & Internals Deep Tech

Hiểu cách Rust tổ chức dữ liệu trong bộ nhớ

Stack vs Heap trong Rust

Nguyên tắc cơ bản

Đặc điểmStackHeap
AllocationCompile-time known sizeRuntime dynamic size
SpeedCực nhanh (pointer bump)Chậm hơn (allocator call)
LifetimeTự động (function scope)Manual hoặc Smart Pointer
AccessLIFO (Last In, First Out)Random access

Khi nào Rust allocate ở đâu?

rust
fn main() {
    // === STACK ALLOCATION ===
    let x: i32 = 42;              // 4 bytes trên stack
    let point: (f64, f64) = (1.0, 2.0);  // 16 bytes trên stack
    let arr: [u8; 100] = [0; 100];       // 100 bytes trên stack
    
    // === HEAP ALLOCATION ===
    let vec: Vec<i32> = vec![1, 2, 3];   // Vec metadata trên stack
                                          // Data [1,2,3] trên heap
    
    let s: String = String::from("Hello"); // String metadata trên stack
                                            // "Hello" bytes trên heap
    
    let boxed: Box<i32> = Box::new(42);    // Box<i32> (pointer) trên stack
                                            // i32 value 42 trên heap
}

Memory Diagram: String Layout

                    STACK                              HEAP
              ┌─────────────────┐            ┌─────────────────────┐
    s: String │ ptr ──────────────────────▶  │ H │ e │ l │ l │ o │
              ├─────────────────┤            └─────────────────────┘
              │ len: 5          │              0   1   2   3   4
              ├─────────────────┤
              │ cap: 5          │
              └─────────────────┘
                   24 bytes                        5 bytes
               (3 × usize on 64-bit)

Giải thích:

  • ptr: Con trỏ 8 bytes trỏ đến vùng nhớ heap
  • len: Số bytes đang sử dụng (5 cho "Hello")
  • cap: Capacity - tổng dung lượng allocated (có thể >= len)

Vec Layout

rust
let v: Vec<i32> = vec![10, 20, 30, 40];
                    STACK                              HEAP
              ┌─────────────────┐            ┌─────┬─────┬─────┬─────┐
   v: Vec<i32>│ ptr ──────────────────────▶  │ 10  │ 20  │ 30  │ 40  │
              ├─────────────────┤            └─────┴─────┴─────┴─────┘
              │ len: 4          │              0     4     8     12  (byte offset)
              ├─────────────────┤
              │ cap: 4          │
              └─────────────────┘
                   24 bytes                      16 bytes (4 × i32)

Sized vs Unsized Types

Sized Trait

Trong Rust, mọi type mặc định đều là Sized — compiler biết size tại compile-time.

rust
// Sized types - compiler biết size lúc compile
let x: i32 = 42;           // 4 bytes
let y: (u64, u64) = (1, 2); // 16 bytes
let z: [u8; 100] = [0; 100]; // 100 bytes

// KHÔNG compile - size không biết lúc compile
// let s: str;  // ERROR: str is unsized
// let a: [i32]; // ERROR: slice is unsized

Dynamically Sized Types (DST)

DST là types mà size chỉ biết lúc runtime:

DSTMô tả
strString slice (UTF-8 bytes)
[T]Slice of T
dyn TraitTrait object

Quy tắc DST:

  1. DST không thể tồn tại trực tiếp trên stack
  2. Phải truy cập qua reference (&str, &[T]) hoặc smart pointer (Box<dyn Trait>)
rust
// ✅ ĐÚNG: DST đằng sau reference
fn print_str(s: &str) { println!("{}", s); }

// ✅ ĐÚNG: DST trong Box
fn process(handler: Box<dyn Fn()>) { handler(); }

// ❌ SAI: DST trực tiếp
// fn bad(s: str) {}  // ERROR: the size of `str` cannot be known at compile-time

Fat Pointers cho DST

Reference đến DST không phải là 1 pointer, mà là 2 pointers (Fat Pointer):

                    Regular Pointer (8 bytes)          Fat Pointer (16 bytes)
                   ┌─────────────────┐               ┌─────────────────┐
    &i32           │ ptr             │    &str       │ ptr             │  → dữ liệu
                   └─────────────────┘               ├─────────────────┤
                                                     │ len (metadata)  │  → length
                                                     └─────────────────┘
rust
use std::mem::size_of;

// Thin pointer
assert_eq!(size_of::<&i32>(), 8);        // 8 bytes (pointer only)
assert_eq!(size_of::<&[i32; 4]>(), 8);   // 8 bytes (sized array)

// Fat pointer
assert_eq!(size_of::<&str>(), 16);       // 16 bytes (ptr + len)
assert_eq!(size_of::<&[i32]>(), 16);     // 16 bytes (ptr + len)
assert_eq!(size_of::<&dyn std::fmt::Debug>(), 16); // 16 bytes (ptr + vtable ptr)

Alignment & Padding

Alignment Rules

CPU đọc memory hiệu quả nhất khi data được aligned — địa chỉ chia hết cho size của type.

TypeSizeAlignment
u8, i81 byte1 byte
u16, i162 bytes2 bytes
u32, i32, f324 bytes4 bytes
u64, i64, f648 bytes8 bytes
usize, pointer8 bytes (64-bit)8 bytes

Struct Padding

Compiler thêm padding bytes để đảm bảo alignment:

rust
// Không tối ưu - có padding
struct BadLayout {
    a: u8,   // 1 byte
    // [3 bytes padding]
    b: u32,  // 4 bytes
    c: u8,   // 1 byte
    // [3 bytes padding]
}
// Total: 12 bytes (thay vì 6 bytes thực tế)

// Tối ưu - sắp xếp theo size giảm dần
struct GoodLayout {
    b: u32,  // 4 bytes (aligned at 0)
    a: u8,   // 1 byte
    c: u8,   // 1 byte
    // [2 bytes padding để struct alignment = 4]
}
// Total: 8 bytes

Memory Layout Diagram:

BadLayout (12 bytes):
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│  a  │ pad │ pad │ pad │  b  │  b  │  b  │  b  │  c  │ pad │ pad │ pad │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
 0     1     2     3     4     5     6     7     8     9     10    11

GoodLayout (8 bytes):
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│  b  │  b  │  b  │  b  │  a  │  c  │ pad │ pad │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
 0     1     2     3     4     5     6     7

Repr Attributes

rust
// #[repr(C)] - Layout như C, predictable
#[repr(C)]
struct FFIStruct {
    a: u8,
    b: u32,
    c: u8,
}
// Giữ nguyên order, có padding như C compiler

// #[repr(packed)] - Không padding, có thể misaligned
#[repr(packed)]
struct PackedStruct {
    a: u8,
    b: u32,  // ⚠️ Có thể unaligned!
    c: u8,
}
// Total: 6 bytes, nhưng access b có thể chậm hơn

// #[repr(align(N))] - Minimum alignment
#[repr(align(64))]
struct CacheAligned {
    data: [u8; 32],
}
// Struct aligned to 64-byte cache line boundary

⚠️ CẢNH BÁO

#[repr(packed)] có thể gây undefined behavior trên một số CPU architectures khi truy cập unaligned data. Chỉ sử dụng khi cần thiết cho FFI hoặc binary protocols.


Zero-Sized Types (ZST)

Types có size = 0 bytes, nhưng vẫn tồn tại trong type system:

rust
use std::mem::size_of;

// ZST examples
struct Empty;
struct Marker;
struct PhantomData<T>(std::marker::PhantomData<T>);

assert_eq!(size_of::<()>(), 0);
assert_eq!(size_of::<Empty>(), 0);
assert_eq!(size_of::<[u8; 0]>(), 0);

// ZST không chiếm memory nhưng có type information
let _: Vec<Empty> = vec![Empty, Empty, Empty];
// Vec header 24 bytes, data 0 bytes!

Use case: Marker types cho type-state pattern, PhantomData cho variance.


Enum Layout

Rust tối ưu enum layout rất tốt:

rust
use std::mem::size_of;

// Simple enum - 1 byte discriminant đủ cho 256 variants
enum Direction {
    North, South, East, West
}
assert_eq!(size_of::<Direction>(), 1);

// Enum with data - discriminant + largest variant
enum Event {
    Click { x: i32, y: i32 },  // 8 bytes
    KeyPress(char),             // 4 bytes
    Quit,                       // 0 bytes
}
assert_eq!(size_of::<Event>(), 12); // 4 (discriminant) + 8 (largest)

// Option<&T> - Null Pointer Optimization (NPO)
assert_eq!(size_of::<Option<&i32>>(), 8);  // Same as &i32!
// None represented as null pointer, no extra discriminant needed

// Option<NonZeroU64> - NPO applies
use std::num::NonZeroU64;
assert_eq!(size_of::<Option<NonZeroU64>>(), 8);  // Same as u64!

Null Pointer Optimization Diagram:

Option<&T> Layout (8 bytes thanks to NPO):

Some(&value):  ┌─────────────────────────┐
               │ 0x7fff_1234_5678 (ptr)  │
               └─────────────────────────┘

None:          ┌─────────────────────────┐
               │  0x0000_0000_0000 (null)│
               └─────────────────────────┘

Without NPO would be 16 bytes:
               ┌──────────┬──────────────┐
               │ tag: 0/1 │ ptr or undef │
               └──────────┴──────────────┘

Bảng Tóm tắt

rust
use std::mem::{size_of, align_of};

// === SIZE & ALIGNMENT ===

// Primitives
assert_eq!(size_of::<u8>(), 1);
assert_eq!(size_of::<u64>(), 8);
assert_eq!(align_of::<u64>(), 8);

// Pointers
assert_eq!(size_of::<&i32>(), 8);      // Thin pointer
assert_eq!(size_of::<&str>(), 16);     // Fat pointer
assert_eq!(size_of::<Box<i32>>(), 8);  // Box is pointer-sized

// Collections on stack
assert_eq!(size_of::<String>(), 24);   // ptr + len + cap
assert_eq!(size_of::<Vec<i32>>(), 24); // ptr + len + cap

// Null Pointer Optimization
assert_eq!(size_of::<Option<&i32>>(), size_of::<&i32>());
assert_eq!(size_of::<Option<Box<i32>>>(), size_of::<Box<i32>>());