Skip to content

Order of allocations in vec! #67

@abgros

Description

@abgros

When you create a vector like

let mut a = vec![vec![42; 45]; 498];

The allocations happen in this order:

  • the last inner vec
  • the outer vec
  • the rest of the inner vecs, in order of index

This is because the vec! macro expands like so:

let a = ::alloc::vec::from_elem(::alloc::vec::from_elem(42, 45), 498);

One inner vector gets allocated, then the outer vector gets allocated (using with_capacity()). Then the initial inner vector gets repeatedly cloned, until the outer vector is filled.

By why does the first inner vector end up last? This is down to an implementation detail in extend_with, which is what ultimately gets called:

// Write all elements except the last one
for _ in 1..n {
	ptr::write(ptr, value.clone());
	ptr = ptr.add(1);
	// Increment the length in every step in case clone() panics
	local_len.increment_len(1);
}

if n > 0 {
	// We can write the last element directly without cloning needlessly
	ptr::write(ptr, value);
	local_len.increment_len(1);
}

This is documented nowhere and extend_with isn't even part of the public API. Nevertheless, it is possible to observe this behaviour by creating a broken implementation of Clone like so:

#[derive(Debug)]
struct Dumb(i32);

// broken implementation of clone
impl Clone for Dumb {
    fn clone(&self) -> Self {
        Dumb(0)
    }
}

fn main() {
	let a = vec![Dumb(439); 5];
	dbg!(a);
}

Which reveals that Dumb(439) is in fact sitting at the back.

This is so obscure and stupid that I think it would be perfect for this quiz.

EDIT: An easier way to observe this would be to simply run the following:

fn main() {
    let vecs = vec![Vec::<u8>::with_capacity(10); 5];
    for v in vecs {
        println!("{}", v.capacity());
    }
}

For which the output is:

0
0
0
0
10

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions