What push does
A Vec is Rust's growable list, and push is how you grow it. The method takes one value and appends it at the end, increasing the length by one. Everything already in the vector stays where it was, so the new element's index is the old length.
let mut arr1: Vec<i32> = vec![1, 2, 3];
arr1.push(4);
println!("{:?}", arr1);
[1, 2, 3, 4]
Appending at the end is cheap. The vector keeps spare capacity and only occasionally needs to allocate a bigger block, so a long series of pushes is efficient on average. Inserting at the front or the middle is a different operation and moves many elements.
Starting from an empty vector
You don't need to know the contents in advance. A common pattern is to create an empty vector and fill it as the program learns the values:
let mut a: Vec<i32> = Vec::new();
a.push(1);
a.push(2);
println!("{:?} {}", a, a.len());
[1, 2] 2
Notice the type annotation Vec<i32>. An empty vector has no elements to infer the type from, but here the compiler could also work it out from the later push calls. When it can't, it asks for the annotation.
Building a vector in a loop
The most frequent use of push is collecting results inside a loop. Start with an empty vector, then push one item per iteration:
let mut squares = Vec::new();
for n in 1..=5 {
squares.push(n * n);
}
println!("{:?} {}", squares, squares.len());
[1, 4, 9, 16, 25] 5
Here the compiler infers the element type from the pushed values, so no annotation is needed. Since each push puts the new item at index len() as measured before the call, the vector ends up in the same order as the loop ran.
Gotchas in Rust
The binding must be mut
Pushing changes the vector, and bindings are immutable by default. Calling push on let v = vec![1, 2, 3] is a compile error: cannot borrow `v` as mutable, as it is not declared as mutable. The compiler's suggestion is the fix: write let mut v.
One type per vector
Every element has the same type. Pushing the string "a" onto a vector of integers fails with a "mismatched types" error. This is also why push takes exactly one argument, and why each call adds one element.
Pushing moves owned values
Pushing an owned value such as a String moves it into the vector. The original variable can't be used afterwards, and the compiler reports "borrow of moved value" if you try. If you still need the original, push a copy with clone().
let mut c: Vec<String> = Vec::new();
let s = String::from("hi");
c.push(s.clone());
c.push(s);
println!("{:?}", c);
["hi", "hi"]
Numbers and string literals are copied on push, so they don't have this restriction.
Push adds one element; extend adds many
To append several values in one call, use extend. For example, e.extend([4, 5]) on vec![1, 2, 3] gives [1, 2, 3, 4, 5].
The lesson's exercise
The editor starts empty, so the exercise has two steps.
- Declare the vectors. Create
fruitswith"mango","kiwi"and"grapefruit", andageswith 14, 32, 50 and 21, both withvec!and both markedmutbecause the next step changes them. - Append the new items. Push
"apple"ontofruits. Push 10 ontoages, then 23. Each call adds one element at the end, so the order of the calls is the order in the vector.
let mut fruits = vec!["mango", "kiwi", "grapefruit"];
let mut ages = vec![14, 32, 50, 21];
fruits.push("apple");
ages.push(10);
ages.push(23);
Afterwards fruits is ["mango", "kiwi", "grapefruit", "apple"] and ages is [14, 32, 50, 21, 10, 23]. Swapping the two push calls on ages would put 23 before 10.
Key takeaways
v.push(x)appends one element at the end and grows the vector by one.- The vector must be declared
let mut. - The pushed value must have the vector's element type.
- Pushing a
Stringmoves it; useclone()to keep the original. - Use
extendto append several values at once.
