Everyone who writes Rust quickly runs into Vec.
When creating a vector with Vec::new() you might expect malloc to be called somewhere in the background. I checked — it isn’t.
I looked into the source to understand why.
Vec::new()
Let’s take a look at the following listing:
fn inspect<T>(v: &Vec<T>) {
println!("ptr: {:p}", v.as_ptr());
println!("cap: {}", v.capacity());
println!("len: {}", v.len());
}
fn main() {
let v: Vec<i32> = Vec::new();
inspect(&v);
}
The output we get is:
ptr: 0x4
cap: 0
len: 0
What path did our code take? Let’s debug the Rust runtime!

We can see where our function ends up — and the source looks like this: Vec::new_in on GitHub
#[inline]
const fn new_in(alloc: A, align: Alignment) -> Self {
let ptr = Unique::from_non_null(NonNull::without_provenance(align.as_nonzero_usize()));
// `cap: 0` means "unallocated". zero-sized types are ignored.
Self { ptr, cap: ZERO_CAP, alloc }
}

The comment alone tells you the principle at work — Zero Cost Abstractions.
When initializing a vector, Rust does it at the lowest possible cost: it sets up a dangling pointer (doc) (ptr: 0x4), but doesn’t actually allocate any memory until it’s needed.
Let’s modify our program, push an element, and see what changes:
fn inspect<T>(v: &Vec<T>) {
println!("ptr: {:p}", v.as_ptr());
println!("cap: {}", v.capacity());
println!("len: {}", v.len());
println!();
}
fn main() {
let mut v: Vec<i32> = Vec::new();
inspect(&v);
v.push(1);
inspect(&v);
}
Output:
ptr: 0x4
cap: 0
len: 0
ptr: 0x559967af6d50
cap: 4
len: 1
As you can see, memory was only allocated after push.
Vec::new() is a perfect example of Rust’s philosophy — you only pay for what you use. (zero-cost abstractions)
