Nugget 16
Homevec! Macro
vec![1, 2, 3] looks like a function call but has a !.
That's your clue: it's a macro, doing compile-time magic that
no regular function could do.
// Vec::new() — creates EMPTY Vec only let mut v: Vec<i32> = Vec::new(); v.push(1); v.push(2); v.push(3); // Three statements! // vec! macro — creates AND populates in one go let v = vec![1, 2, 3]; // One expression!
That's it in a nutshell. But why a macro? Why not a function
Vec::from_elements(&[1, 2, 3])?
vec! Actually Does
At compile time, vec![1, 2, 3] expands to something like this:
// What you write:
let v = vec![1, 2, 3];
// What the compiler generates:
let v = {
let mut v = Vec::with_capacity(3); // Pre-allocates!
v.push(1);
v.push(2);
v.push(3);
v
};
The macro knows the element count at compile time, so it calls
with_capacity — one allocation, no reallocation.
vec! Can Dolet v = vec![1, 2, 3, 4, 5]; // ✅ Clear, concise let v = Vec::new(); // Empty — must push in a loop
vec![val; count])let zeros = vec![0; 100]; // ✅ 100 zeros, one expression
let falses = vec![false; 50]; // ✅ 50 false values
// Without the macro: loop with 100 pushes
let mut zeros = Vec::with_capacity(100);
for _ in 0..100 { zeros.push(0); }
#[derive(Clone)]
struct Point { x: i32, y: i32 }
let points = vec![
Point { x: 0, y: 0 },
Point { x: 1, y: 0 },
Point { x: 0, y: 1 },
]; // ✅ Works fine — values provided explicitly
let same = vec![Point { x: 0, y: 0 }; 3]; // ✅ Repeats — needs Clone
Rust could have designed Vec with static methods:
// Hypothetical (doesn't exist): Vec::from_elements(&[1, 2, 3]); // From slice Vec::from_repeated(0, 100); // Repeat value // But these are more verbose and less flexible.
Rust chose macros over variadic functions (functions with variable arguments).
Variadic functions complicate the type system — how do you type-check
vec(1, 2, 3) vs vec(1, 2, 3, 4) with different
lengths? A macro generates code at compile time, so there's no type-system
headache.
// Other languages handle this differently:
// JavaScript: [1, 2, 3] — array literal, built into the language
// C: int arr[] = {1, 2, 3}; — also a language feature, not a library
// Python: [1, 2, 3] — list literal, syntax-level
// Rust: vec![1, 2, 3] — a library macro, not language syntax
The ! is your clue: compile-time code generation is happening.
Rust keeps the language core small and pushes convenience into macros.
vec! Can't DoThe macro is for when you know the values at compile time. For dynamic data, use iterators:
// ❌ This doesn't create vec![0, 1, 2, 3, 4] let v = vec![0..5]; // Creates vec![Range] — a Vec with one Range element! // ✅ Use collect for dynamic ranges let v: Vec<i32> = (0..5).collect(); // [0, 1, 2, 3, 4] // ✅ Use collect for transformation let v: Vec<i32> = (0..5).map(|x| x * 10).collect(); // [0, 10, 20, 30, 40]
💡 vec! vs collect()
When you know the elements: vec![1, 2, 3]. When you're
transforming or generating: (0..5).map(f).collect().
They complement each other — the macro for literals, iterators for
computed sequences.
The macro knows the element count at compile time, so it pre-allocates the exact capacity:
// vec![1, 2, 3] generates: let mut v = Vec::with_capacity(3); // Exact size known at compile time v.push(1); v.push(2); v.push(3); // No reallocations — exactly one heap allocation of the right size. // Compare with dynamic building: let mut v = Vec::new(); v.push(1); // May allocate (capacity = some default) v.push(2); // May reallocate v.push(3); // May reallocate again // Up to 3 allocations instead of 1.
So vec! isn't just convenient — it's also the most efficient way
to create a Vec from known elements.
vec! is a macro (!), not a function — it generates code at compile time.vec![a, b, c] for literal elements, vec![val; n] for repeated values.! signals compile-time magic.vec![0; 100] pre-allocates exactly — more efficient than pushing in a loop.(0..n).collect() for dynamic/computed sequences, not vec!.