777 words
4 minutes
240703_Array_vs_Vec_memory_diagrams
2024-07-03

link#


  • I’ll explain Array vs Vec memory layout, where data lives, what’s on stack vs heap, and why performance differs, using clear CLI diagrams you can see with your eyes.

🧠 Big picture (pin this first)#

Array ([T; N]) → size known at compile time
Vec<T>         → size known at runtime
  • Memory-wise:
Array → inline storage
Vec   → pointer to heap storage

Summary table (pin this)|🔝|#

PropertyArray [T; N]Vec
Size knownCompile timeRuntime
StorageInlineHeap
Grows❌ No✅ Yes
Reallocation❌ No✅ Yes
Indirection❌ No✅ Yes
Cache-friendlyExcellentExcellent
Stack usageHigh if largeSmall

Final mental model (one sentence)|🔝|#

  • Array owns the bytes directly.

  • Vec owns a pointer to bytes.

    • 배열이 바이트를 직접 소유합니다.
    • Vec는 바이트 포인터를 소유하고 있습니다.
  • Or visually:

Array = [ DATA ]
Vec   = [ POINTER ] → [ DATA ]

1️⃣ Fixed-size Array [T; N] memory diagram|🔝|#

  • Rust code
let a: [i32; 4] = [10, 20, 30, 40];
  • Memory layout (typically on stack)
Stack
┌───────────────────────────────┐
│ a[0] │ a[1] │ a[2] │ a[3]     │
│  10  │  20  │  30  │  40      │
└───────────────────────────────┘
  • Key properties:
✔ One contiguous block
✔ No pointer indirection
✔ Size = 4 * size_of(i32)
✔ Known at compile time
  • The array is the data.

2️⃣ Array inside a struct (still inline)|🔝|#

struct S {
    a: [i32; 4],
    b: i32,
}
  • Memory:
Stack (or heap if S is heap-allocated)
┌─────────────────────────────────────┐
│ a[0] a[1] a[2] a[3] │ b             │
└─────────────────────────────────────┘
  • 🔥 Arrays are always inline, never indirect.

3️⃣ Vec<T> memory diagram (three-part structure)|🔝|#

  • Rust code
let v: Vec<i32> = vec![10, 20, 30, 40];
  • What Vec<T> really is
Vec<T> = (ptr, len, capacity)
  • Stack vs Heap
Stack                         Heap
┌─────────────────────┐      ┌───────────────────────────────┐
│ ptr ────────────────┼────▶ │ v[0] │ v[1] │ v[2] │ v[3]     │
│ len = 4             │      │  10  │  20  │  30  │  40      │
│ cap = 4             │      └───────────────────────────────┘
└─────────────────────┘
  • Key properties:
✔ Vec header is small (3 machine words)
✔ Data lives on the heap
✔ Size can grow/shrink
  • The vector points to the data, it is not the data.

4️⃣ Side-by-side: Array vs Vec (visual)|🔝|#

Array [i32; 4]:

┌───────────────┐
│ 10 20 30 40   │  ← inline, fixed
└───────────────┘


Vec<i32>:

┌───────────────┐        ┌───────────────┐
│ ptr len cap   │ ────▶  │ 10 20 30 40   │
└───────────────┘        └───────────────┘

5️⃣ Where they live (important)|🔝|#

  • Local variable
let a = [1, 2, 3, 4];
let v = vec![1, 2, 3, 4];
Stack:
- a  → full array
- v  → only (ptr, len, cap)
Heap:
- v’s elements

6️⃣ Access cost (what the CPU does)|🔝|#

Array access#

a[i]
  • CPU:
base_address + i * sizeof(T)
  • ✔ Single address computation
  • ✔ No pointer chasing

Vec access#

v[i]
  • CPU:
load ptr
ptr + i * sizeof(T)
  • ❌ One extra load (usually cached)
  • ✔ Still fast

7️⃣ Cache behavior (important difference)|🔝|#

Array#

┌────────────────────────────────┐
│ a0 a1 a2 a3 a4 a5 a6 a7 ...    │
└────────────────────────────────┘
  • Always contiguous
  • Perfect for cache + SIMD

Vec#

Stack: [ptr len cap]
Heap:  ┌────────────────────────┐
       │ v0 v1 v2 v3 v4 ...     │
       └────────────────────────┘
  • Still contiguous
  • But one level of indirection
  • Heap placement may affect locality

8️⃣ Growing a Vec (reallocation diagram)|🔝|#

v.push(50);
  • If capacity is exceeded:
Old heap:
┌───────────────┐
│ 10 20 30 40   │
└───────────────┘

Allocate new heap:
┌───────────────────────────┐
│ 10 20 30 40 50             │
└───────────────────────────┘

Update ptr, len, cap
Free old heap
  • 🔥 This cannot happen with arrays.

9️⃣ Why arrays can be faster (sometimes)|🔝|#

✔ No heap allocation
✔ No reallocation
✔ Better cache predictability
✔ Better for SIMD
  • But:
❌ Fixed size
❌ Large arrays may overflow stack

🔟 Why Vec is used everywhere|🔝|#

✔ Runtime-sized
✔ Can grow/shrink
✔ Easy to pass around
✔ Standard collection
  • Rust APIs prefer:
&[T]  // slice
  • Because both arrays and Vec can produce slices.

1️⃣1️⃣ Slice unifies both worlds|🔝|#

  • 슬라이스는 두 세계를 통합합니다
fn foo(x: &[i32]) {}
Array → &[i32]
Vec   → &[i32]
  • Memory:
Slice = (ptr, len)
┌───────────────┐
│ ptr len       │
└───────────────┘
  • No ownership, just a view.
240703_Array_vs_Vec_memory_diagrams
https://younghakim7.github.io/blog/posts/240703_array_vs_vec_memory_diagrams/
Author
YoungHa
Published at
2024-07-03