Każdy kto pisze w Rust szybko trafia na Vec.
Tworząc wektor przez Vec::new() można by się spodziewać, że gdzieś w tle wywołuje się malloc.
Sprawdziłem — nie wywołuje.
Zajrzałem do źródeł żeby zrozumieć dlaczego.
Vec::new()
Przyjrzyjmy się poniższemu listingowi:
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);
}
Wynikowa wartość którą otrzymujemy to:
ptr: 0x4
cap: 0
len: 0
Jaką drogę przebył nasz kod? Spróbujmy zdebugować runtime Rusta!

Wiemy gdzie nasza funkcja docelowo ląduje — a kod źródłowy prezentuje się tak: Vec::new_in na GitHubie
#[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 }
}

Powyższy kod już po komentarzu pokazuje zasadę której hołduje — Zero Cost Abstractions.
Rust inicjalizując wektor robi to najmniejszym kosztem: przygotowuje dangling pointer (doc) (ptr: 0x4), ale faktycznie bez potrzeby nie alokuje pamięci.
Zatem zmodyfikujmy nasz program, dodajmy element i zobaczmy co się zmieni:
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
Jak widać, dopiero po push faktycznie została przydzielona pamięć.
Z tego krótkiego artykułu udało nam się dowiedzieć, że Vec::new() to przykład filozofii Rusta — płacisz tylko za to czego używasz. (zero-cost abstractions)
