5/5
## Understanding and Using Vectors in Rust Vectors are a fundamental and versatile collection type in Rust, offering a dynamic way to store a list of elements. This lesson will guide you through the essentials of working with vectors, from their creation and manipulation to accessing and slicing their contents. ## Introduction to Vectors At their core, vectors in Rust are similar to arrays: they are collections of elements that must all share the same data type. However, they possess a crucial distinction that sets them apart and makes them incredibly useful in many programming scenarios. The key difference lies in their size management: * **Arrays:** Have a fixed size that is determined and known at compile time. Once an array is declared with a certain length, that length cannot change. * **Vectors:** Are dynamically-sized. Their size can grow or shrink at runtime as elements are added or removed. This flexibility makes vectors ideal when the number of elements is not known beforehand or is expected to change during program execution. ## Creating Vectors Rust provides several ways to create vectors, catering to different initialization needs. ### Creating an Empty Vector with `Vec::new()` To create an empty vector, you can use the `Vec::new()` associated function. When doing so, you must explicitly specify the type of elements the vector will hold. If you intend to add elements to this vector later, it must be declared as mutable using the `mut` keyword. ```rust fn main() { let mut v: Vec<i32> = Vec::new(); } ``` In this example, `let mut v: Vec<i32> = Vec::new();` initializes an empty vector named `v` that is designated to store `i32` (32-bit integer) values. The `mut` keyword allows us to modify `v` after its creation. ### Adding Elements with `push()` Once you have a mutable vector, you can add elements to its end using the `push()` method. ```rust fn main() { let mut v: Vec<i32> = Vec::new(); v.push(1); v.push(2); v.push(3); } ``` Here, we successively append the integers 1, 2, and 3 to our vector `v`. ### Printing Vectors To inspect the contents of a vector, you can print it using the `println!` macro combined with the debug formatter `{:?}`. ```rust fn main() { let mut v: Vec<i32> = Vec::new(); v.push(1); v.push(2); v.push(3); println!("v: {:?}", v); } ``` Executing this code will produce the output: `v: [1, 2, 3]`. ### Creating Vectors with Initial Values using the `vec!` Macro If you know the initial elements of your vector at the time of creation, the `vec!` macro offers a more concise and convenient syntax. The Rust compiler is often able to infer the type of the vector's elements. For numeric types, it defaults to `i32`. ```rust fn main() { let v = vec![1, 2, 3]; // Rust infers v is Vec<i32> // For explicit type annotation: // let v: Vec<i32> = vec![1, 2, 3]; } ``` The `vec![1, 2, 3]` macro creates a new vector initialized with the elements 1, 2, and 3. ### Specifying Element Types with the `vec!` Macro If you require a vector of a type other than the compiler's default (e.g., `i8` for 8-bit signed integers or `u8` for 8-bit unsigned integers), you can specify the type in a couple of ways: 1. **Explicit Type Annotation:** Add a type annotation directly to the variable declaration. ```rust fn main() { let v: Vec<i8> = vec![1, 2, 3]; } ``` 2. **Type Suffix on an Element:** Add a type suffix (like `u8`, `i16`, etc.) to one of the elements within the `vec!` macro. The compiler will then infer the vector's type from that specific element. ```rust fn main() { let v = vec![1u8, 2, 3]; // v is now Vec<u8> } ``` In this case, because `1u8` is specified, `v` becomes a `Vec<u8>`. ### Creating a Vector with Repeating Values The `vec!` macro also provides a handy syntax for creating a vector containing a specified number of identical elements: `vec![value; count]`. ```rust fn main() { // Create a vector of 100 elements, all initialized to 0 of type i8. let v: Vec<i8> = vec![0i8; 100]; println!("v: {:?}", v); } ``` Running this code will print a vector containing one hundred `0`s, for example: `v: [0, 0, 0, ..., 0]`. ## Accessing Elements in a Vector Rust offers two primary methods for accessing elements within a vector, each with different safety implications. ### Using Index Notation (Unsafe) Elements can be accessed directly using square bracket notation: `v[index]`. This method provides direct access to the element at the specified zero-based index. **Important:** This approach is considered "unsafe" in the sense that if you attempt to access an index that is out of bounds (i.e., an index greater than or equal to the vector's length, or a negative index), your program will **panic** at runtime and terminate. ```rust fn main() { let v: Vec<i8> = vec![10, 20, 30]; println!("Element at index 1: {}", v[1]); // Accesses the element 20 // The following line would cause a panic: // println!("Accessing out of bounds: {}", v[5]); } ``` If an out-of-bounds access like `v[1000]` were attempted on a vector of length 100, the program would panic with a message similar to: `thread 'main' panicked at 'index out of bounds: the len is 100 but the index is 1000'`. ### Using the `get()` Method (Safe) A safer and more robust way to access elements is by using the `get()` method. This method does not cause a panic if an invalid index is provided. Instead, `v.get(index)` returns an `Option<&T>`, where `T` is the type of elements in the vector. * If the `index` is valid (within the bounds of the vector), `get()` returns `Some(&value)`, where `&value` is a reference to the element at that index. * If the `index` is out of bounds, `get()` returns `None`. This mechanism allows you to handle potential out-of-bounds access gracefully using Rust's `Option` enum, typically with a `match` statement or methods like `unwrap_or`. ```rust fn main() { let v: Vec<i8> = vec![0i8; 100]; // Vector of 100 zeros // Accessing a valid index: // v.get(1) returns Option<&i8> // Since index 1 is valid, it returns Some(&value_at_index_1) println!("v.get(1): {:?}", v.get(1)); // Accessing an invalid index: // Since index 1000 is invalid, it returns None println!("v.get(1000): {:?}", v.get(1000)); } ``` Executing this code will output: `v.get(1): Some(0)` `v.get(1000): None` ## Updating Elements To modify an existing element at a specific index within a vector, the vector must first be declared as mutable (using `mut`). You can then use the index notation on the left side of an assignment operation. ```rust fn main() { let mut v: Vec<i8> = vec![1, 2, 3]; println!("Original v: {:?}", v); // v: [1, 2, 3] v[0] = 99; // Updates the element at index 0 from 1 to 99 println!("Updated v: {:?}", v); // v: [99, 2, 3] } ``` As with accessing elements, attempting to update an element at an out-of-bounds index using this method will result in a panic. ## Removing Elements with `pop()` The `pop()` method provides a way to remove the **last** element from a vector. It also returns this removed element. For `pop()` to be used, the vector must be mutable. Similar to the `get()` method, `pop()` returns an `Option<T>` (note: it's `Option<T>`, not `Option<&T>`, because `pop()` takes ownership of the removed element, moving it out of the vector). * If the vector is not empty, `pop()` removes the last element and returns `Some(value)`, where `value` is the element that was removed. * If the vector is empty, `pop()` does nothing to the vector and returns `None`. ```rust fn main() { let mut v: Vec<i8> = vec![1, 2, 3]; println!("Initial v: {:?}", v); let x1: Option<i8> = v.pop(); println!("Popped: {:?}, v after pop: {:?}", x1, v); // Popped: Some(3), v after pop: [1, 2] let x2: Option<i8> = v.pop(); println!("Popped: {:?}, v after pop: {:?}", x2, v); // Popped: Some(2), v after pop: [1] let x3: Option<i8> = v.pop(); println!("Popped: {:?}, v after pop: {:?}", x3, v); // Popped: Some(1), v after pop: [] let x4: Option<i8> = v.pop(); // Vector is now empty println!("Popped: {:?}, v after pop: {:?}", x4, v); // Popped: None, v after pop: [] } ``` This demonstrates how `pop()` removes elements from the end and how it behaves when the vector becomes empty. ## Slices from Vectors Similar to arrays, you can create a slice from a vector. A slice is a reference to a contiguous sequence of elements within a vector. Slices allow you to borrow a portion of a vector without taking ownership or copying the data. The syntax for creating a slice involves taking a reference to a range of the vector: `&v[start_index..end_index]`. * `start_index` is inclusive (the element at this index is included in the slice). * `end_index` is exclusive (the element at this index is *not* included in the slice). ```rust fn main() { let v = vec![1, 2, 3, 4, 5]; // Create a slice containing elements from index 1 up to (but not including) index 4. // This will include v[1], v[2], and v[3], which are the values 2, 3, and 4. let s: &[i32] = &v[1..4]; println!("Slice s: {:?}", s); } ``` Executing this code will output: `Slice s: [2, 3, 4]`. Slices are a powerful feature for referencing parts of collections efficiently.
Vectors are a fundamental and versatile collection type in Rust, offering a dynamic way to store a list of elements. This lesson will guide you through the essentials of working with vectors, from their creation and manipulation to accessing and slicing their contents.
At their core, vectors in Rust are similar to arrays: they are collections of elements that must all share the same data type. However, they possess a crucial distinction that sets them apart and makes them incredibly useful in many programming scenarios.
The key difference lies in their size management:
Arrays: Have a fixed size that is determined and known at compile time. Once an array is declared with a certain length, that length cannot change.
Vectors: Are dynamically-sized. Their size can grow or shrink at runtime as elements are added or removed. This flexibility makes vectors ideal when the number of elements is not known beforehand or is expected to change during program execution.
Rust provides several ways to create vectors, catering to different initialization needs.
Vec::new()
To create an empty vector, you can use the Vec::new()
associated function. When doing so, you must explicitly specify the type of elements the vector will hold. If you intend to add elements to this vector later, it must be declared as mutable using the mut
keyword.
In this example, let mut v: Vec<i32> = Vec::new();
initializes an empty vector named v
that is designated to store i32
(32-bit integer) values. The mut
keyword allows us to modify v
after its creation.
push()
Once you have a mutable vector, you can add elements to its end using the push()
method.
Here, we successively append the integers 1, 2, and 3 to our vector v
.
To inspect the contents of a vector, you can print it using the println!
macro combined with the debug formatter {:?}
.
Executing this code will produce the output: v: [1, 2, 3]
.
vec!
MacroIf you know the initial elements of your vector at the time of creation, the vec!
macro offers a more concise and convenient syntax. The Rust compiler is often able to infer the type of the vector's elements. For numeric types, it defaults to i32
.
The vec![1, 2, 3]
macro creates a new vector initialized with the elements 1, 2, and 3.
vec!
MacroIf you require a vector of a type other than the compiler's default (e.g., i8
for 8-bit signed integers or u8
for 8-bit unsigned integers), you can specify the type in a couple of ways:
Explicit Type Annotation: Add a type annotation directly to the variable declaration.
Type Suffix on an Element: Add a type suffix (like u8
, i16
, etc.) to one of the elements within the vec!
macro. The compiler will then infer the vector's type from that specific element.
In this case, because 1u8
is specified, v
becomes a Vec<u8>
.
The vec!
macro also provides a handy syntax for creating a vector containing a specified number of identical elements: vec![value; count]
.
Running this code will print a vector containing one hundred 0
s, for example: v: [0, 0, 0, ..., 0]
.
Rust offers two primary methods for accessing elements within a vector, each with different safety implications.
Elements can be accessed directly using square bracket notation: v[index]
. This method provides direct access to the element at the specified zero-based index.
Important: This approach is considered "unsafe" in the sense that if you attempt to access an index that is out of bounds (i.e., an index greater than or equal to the vector's length, or a negative index), your program will panic at runtime and terminate.
If an out-of-bounds access like v[1000]
were attempted on a vector of length 100, the program would panic with a message similar to: thread 'main' panicked at 'index out of bounds: the len is 100 but the index is 1000'
.
get()
Method (Safe)A safer and more robust way to access elements is by using the get()
method. This method does not cause a panic if an invalid index is provided. Instead, v.get(index)
returns an Option<&T>
, where T
is the type of elements in the vector.
If the index
is valid (within the bounds of the vector), get()
returns Some(&value)
, where &value
is a reference to the element at that index.
If the index
is out of bounds, get()
returns None
.
This mechanism allows you to handle potential out-of-bounds access gracefully using Rust's Option
enum, typically with a match
statement or methods like unwrap_or
.
Executing this code will output:
v.get(1): Some(0)
v.get(1000): None
To modify an existing element at a specific index within a vector, the vector must first be declared as mutable (using mut
). You can then use the index notation on the left side of an assignment operation.
As with accessing elements, attempting to update an element at an out-of-bounds index using this method will result in a panic.
pop()
The pop()
method provides a way to remove the last element from a vector. It also returns this removed element. For pop()
to be used, the vector must be mutable.
Similar to the get()
method, pop()
returns an Option<T>
(note: it's Option<T>
, not Option<&T>
, because pop()
takes ownership of the removed element, moving it out of the vector).
If the vector is not empty, pop()
removes the last element and returns Some(value)
, where value
is the element that was removed.
If the vector is empty, pop()
does nothing to the vector and returns None
.
This demonstrates how pop()
removes elements from the end and how it behaves when the vector becomes empty.
Similar to arrays, you can create a slice from a vector. A slice is a reference to a contiguous sequence of elements within a vector. Slices allow you to borrow a portion of a vector without taking ownership or copying the data.
The syntax for creating a slice involves taking a reference to a range of the vector: &v[start_index..end_index]
.
start_index
is inclusive (the element at this index is included in the slice).
end_index
is exclusive (the element at this index is not included in the slice).
Executing this code will output: Slice s: [2, 3, 4]
. Slices are a powerful feature for referencing parts of collections efficiently.
A dynamic introduction to Rust Vectors - Learn to create, populate, and manage dynamically-sized collections using `Vec::new()`, `vec!`, and `push()`. Explore safe element access with `get()` versus indexing, updating, removing with `pop()`, and creating vector slices.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on July 11, 2025
Rust Developer
Rust Programming BasicsDuration: 6min
Duration: 18min
Duration: 47min
Duration: 15min
Duration: 19min
Duration: 8min
Duration: 12min
Duration: 46min
Duration: 14min
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on July 11, 2025