Reputation: 149
let vec_macro = vec![0, 1, 2, 3, 4];
let mut vec = Vec::new();
for i in 0..5 {
vec.push(i)
}
println!("Capacity of vec with macro: {}", vec_macro.capacity());
println!("Capacity of vec push: {}", vec.capacity());
// Result:
// Capacity of vec with macro: 5
// Capacity of vec push: 8
I created 2 vectors, first one with macro vec!
and the second one with Vec::new()
, then I push item from 0 to 4 into them. The expected result is the capacity of these two vectors are the same, but it's not. Is this the bug in the implementation of vec!
?
Upvotes: 1
Views: 730
Reputation: 16910
The macro knows the number of elements, so it creates the vector with the optimal capacity (see also with_capacity()
).
Your loop invokes push()
several times, but this operation does not know by itself when we will stop pushing elements.
So the strategy behind this is to amortize the cost of dynamic memory reallocation with an exponential capacity (0, 4, 8, 16, 32... for example on my computer).
When you push very few elements, the capacity stays low (we do not waste too much unused allocated memory), but if you push many elements, the reallocations will grow by bigger and bigger steps in order to happen not too often.
At the moment, we see that it simply doubles the capacity (whatever it is) when it is exhausted. If we start from zero then just push, it seems to choose 4 as initial capacity then doubles it as needed, but if we start with 5 it will still continue to double the capacity (10, 20...). However, what we see now is not guaranteed and might change at any time in future releases.
Upvotes: 6
Reputation: 15084
vec![a, b, ...]
is not the same as a repeated push. If you look at the source code for the macro, it looks like this:
($($x:expr),+ $(,)?) => (
$crate::__rust_force_expr!(<[_]>::into_vec(box [$($x),+]))
);
The important part is the <[_]>::into_vec(box [$($x),+])
:
box [$($x),+]
is special syntax to create a "boxed slice" directly on the heap, as opposed to creating it on the stack and then moving it to the heap<[_]>::into_vec
calls slice::into_vec
If we look at the source code of into_vec
:
pub fn into_vec<T, A: Allocator>(b: Box<[T], A>) -> Vec<T, A> {
unsafe {
let len = b.len();
let (b, alloc) = Box::into_raw_with_allocator(b);
Vec::from_raw_parts_in(b as *mut T, len, len, alloc)
}
}
And the signature of Vec::from_raw_parts_in
:
pub unsafe fn from_raw_parts_in(
ptr: *mut T,
length: usize,
capacity: usize,
alloc: A
) -> Self;
You can see that the slice length is passed as both the Vec
length and capacity.
The Vec
documentation specifies this behavior of the macro:
vec![x; n]
,vec![a, b, c, d]
, and [Vec::with_capacity(n)
][Vec::with_capacity
], will all produce aVec
with exactly the requested capacity. If[len] == [capacity]
, (as is the case for the [vec!
] macro), then aVec<T>
can be converted to and from a [Box<[T]>
][owned slice] without reallocating or moving the elements.
As prog-fh said, Vec::push
will exponentially reallocate when necessary, and will not end on a capacity of 5
unless you otherwise set the capacity manually via with_capacity
or otherwise. It should be noted that this exponential growth is an implementation detail, not guaranteed by the Vec
documentation.
Upvotes: 2