Introduction to Vectors in Rust
Vectors (Vec<T>) are one of the most commonly used collections in Rust. They provide a dynamic, growable array that stores elements of the same type in contiguous memory. Vectors are heap-allocated and offer flexible sizing while maintaining fast access times.
Key Concepts
- Dynamic Sizing: Grows and shrinks at runtime
- Type Safety: All elements must be the same type
- Memory Contiguous: Elements stored sequentially in memory
- Ownership: Vector owns its elements
- Performance: O(1) index access, amortized O(1) push
1. Creating Vectors
Basic Creation Methods
fn main() {
// Method 1: Vec::new() - empty vector
let mut v1: Vec<i32> = Vec::new();
v1.push(1);
v1.push(2);
println!("v1: {:?}", v1);
// Method 2: vec! macro - with initial values
let v2 = vec![1, 2, 3, 4, 5];
println!("v2: {:?}", v2);
// Method 3: vec! macro with repeated value
let v3 = vec![0; 10]; // Creates vector with 10 zeros
println!("v3: {:?}", v3);
// Method 4: From iterator
let v4: Vec<i32> = (0..5).collect();
println!("v4: {:?}", v4);
// Method 5: From array
let arr = [1, 2, 3, 4, 5];
let v5 = Vec::from(arr);
println!("v5: {:?}", v5);
// Method 6: with_capacity - pre-allocate space
let mut v6 = Vec::with_capacity(10);
println!("v6 capacity: {}, length: {}", v6.capacity(), v6.len());
// Method 7: From raw parts (advanced, unsafe)
let ptr = Box::into_raw(Box::new([1, 2, 3])) as *mut i32;
let v7 = unsafe { Vec::from_raw_parts(ptr, 3, 3) };
println!("v7: {:?}", v7);
}
Type Annotations
fn main() {
// Explicit type annotation
let v1: Vec<i32> = Vec::new();
let v2: Vec<&str> = Vec::new();
let v3: Vec<String> = Vec::new();
// Type inference from usage
let mut v4 = Vec::new();
v4.push(42); // Now v4 is Vec<i32>
let mut v5 = Vec::new();
v5.push("hello"); // Now v5 is Vec<&str>
// Using turbofish syntax
let v6 = Vec::<i32>::new();
let v7 = vec![1, 2, 3]; // Vec<i32> inferred
// Complex types
let v8: Vec<Vec<i32>> = vec![
vec![1, 2, 3],
vec![4, 5, 6],
vec![7, 8, 9],
];
println!("v8: {:?}", v8);
}
2. Accessing Elements
Indexing and Safe Access
fn main() {
let v = vec![10, 20, 30, 40, 50];
// Direct indexing (panics if out of bounds)
let third = v[2];
println!("Third element: {}", third);
// Safe access with get() (returns Option)
match v.get(2) {
Some(value) => println!("Third element safely: {}", value),
None => println!("No third element"),
}
// Safe access to non-existent index
match v.get(10) {
Some(value) => println!("Element at 10: {}", value),
None => println!("No element at index 10"),
}
// First and last elements
if let Some(first) = v.first() {
println!("First: {}", first);
}
if let Some(last) = v.last() {
println!("Last: {}", last);
}
// Getting slices
let slice = &v[1..4]; // [20, 30, 40]
println!("Slice: {:?}", slice);
// Getting mutable references
let mut v = vec![1, 2, 3, 4, 5];
if let Some(element) = v.get_mut(2) {
*element = 100;
}
println!("After modification: {:?}", v);
}
Multiple Element Access
fn main() {
let v = vec![1, 2, 3, 4, 5];
// Get multiple elements
let first_two = &v[0..2];
println!("First two: {:?}", first_two);
// Get all except first
let tail = &v[1..];
println!("Tail: {:?}", tail);
// Get all except last
let init = &v[..v.len()-1];
println!("Init: {:?}", init);
// Split into two at index
let (left, right) = v.split_at(3);
println!("Left: {:?}, Right: {:?}", left, right);
// Chunks
let chunks: Vec<&[i32]> = v.chunks(2).collect();
println!("Chunks of 2: {:?}", chunks);
// Windows (overlapping chunks)
let windows: Vec<&[i32]> = v.windows(3).collect();
println!("Windows of 3: {:?}", windows);
}
3. Modifying Vectors
Adding Elements
fn main() {
// push - add to end
let mut v = Vec::new();
v.push(1);
v.push(2);
v.push(3);
println!("After push: {:?}", v);
// insert at position
v.insert(1, 10); // Insert 10 at index 1
println!("After insert: {:?}", v);
// append - move all elements from another vector
let mut v2 = vec![4, 5, 6];
v.append(&mut v2);
println!("After append: {:?}, v2 empty: {:?}", v, v2);
// extend - add elements from iterator
v.extend(vec![7, 8, 9]);
println!("After extend: {:?}", v);
// extend from slice
v.extend_from_slice(&[10, 11, 12]);
println!("After extend_from_slice: {:?}", v);
// reserve space
let mut v = Vec::with_capacity(2);
println!("Capacity: {}", v.capacity());
v.reserve(10); // Ensure capacity for at least 10 more
println!("Capacity after reserve: {}", v.capacity());
}
Removing Elements
fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// pop - remove last element
if let Some(last) = v.pop() {
println!("Popped: {}", last);
}
println!("After pop: {:?}", v);
// remove at index
let removed = v.remove(2); // Remove element at index 2
println!("Removed at index 2: {}", removed);
println!("After remove: {:?}", v);
// remove with swap (doesn't preserve order but O(1))
let mut v = vec![1, 2, 3, 4, 5];
let removed = v.swap_remove(1); // Remove element at index 1, replace with last
println!("Swap removed: {}, vector: {:?}", removed, v);
// truncate to length
let mut v = vec![1, 2, 3, 4, 5];
v.truncate(3);
println!("Truncated to 3: {:?}", v);
// clear all elements
v.clear();
println!("Cleared: {:?}, len: {}", v, v.len());
// drain - remove range
let mut v = vec![1, 2, 3, 4, 5];
let drained: Vec<_> = v.drain(1..4).collect();
println!("Drained: {:?}, remaining: {:?}", drained, v);
// retain - keep elements satisfying condition
let mut v = vec![1, 2, 3, 4, 5, 6];
v.retain(|&x| x % 2 == 0);
println!("After retain (evens): {:?}", v);
// dedup - remove consecutive duplicates
let mut v = vec![1, 1, 2, 2, 3, 1, 1, 4];
v.dedup();
println!("After dedup: {:?}", v); // [1, 2, 3, 1, 4]
}
Modifying Elements
fn main() {
// Modify by index
let mut v = vec![1, 2, 3, 4, 5];
v[2] = 30;
println!("After modification: {:?}", v);
// Modify using get_mut
if let Some(value) = v.get_mut(3) {
*value = 40;
}
println!("After get_mut: {:?}", v);
// Swap elements
v.swap(0, 4); // Swap first and last
println!("After swap: {:?}", v);
// Replace with new value, return old
let old = std::mem::replace(&mut v[1], 100);
println!("Replaced {} with 100, vector: {:?}", old, v);
// Update all elements with map in place
for x in &mut v {
*x *= 2;
}
println!("After doubling: {:?}", v);
// Using resize to change length with default value
let mut v = vec![1, 2, 3];
v.resize(5, 0);
println!("After resize to 5: {:?}", v);
v.resize(2, 0);
println!("After resize to 2: {:?}", v);
// resize with custom default
let mut v = vec!["a", "b", "c"];
v.resize_with(5, || "default");
println!("After resize_with: {:?}", v);
}
4. Iterating Over Vectors
Different Iteration Methods
fn main() {
let v = vec![10, 20, 30, 40, 50];
// Immutable iteration
println!("Immutable iteration:");
for value in &v {
print!("{} ", value);
}
println!();
// Mutable iteration
println!("Mutable iteration:");
let mut v = vec![1, 2, 3, 4, 5];
for value in &mut v {
*value *= 2;
print!("{} ", value);
}
println!("\nAfter mutation: {:?}", v);
// By value (consumes vector)
let v = vec![1, 2, 3];
for value in v {
print!("{} ", value);
}
println!();
// println!("{:?}", v); // Error: v moved
// With index (enumerate)
let v = vec![10, 20, 30];
for (index, value) in v.iter().enumerate() {
println!("Index {}: {}", index, value);
}
// Using iterators explicitly
let v = vec![1, 2, 3];
let mut iter = v.iter();
while let Some(value) = iter.next() {
println!("Iterator next: {}", value);
}
}
Functional Iteration
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// map
let doubled: Vec<_> = numbers.iter().map(|&x| x * 2).collect();
println!("Doubled: {:?}", doubled);
// filter
let evens: Vec<_> = numbers.iter().filter(|&&x| x % 2 == 0).collect();
println!("Evens: {:?}", evens);
// filter_map
let strings = vec!["1", "2", "abc", "3", "def"];
let parsed: Vec<i32> = strings.iter()
.filter_map(|s| s.parse().ok())
.collect();
println!("Parsed numbers: {:?}", parsed);
// fold (reduce)
let sum = numbers.iter().fold(0, |acc, &x| acc + x);
println!("Sum: {}", sum);
// any and all
let has_even = numbers.iter().any(|&x| x % 2 == 0);
let all_positive = numbers.iter().all(|&x| x > 0);
println!("Has even: {}, All positive: {}", has_even, all_positive);
// find and position
if let Some(first_even) = numbers.iter().find(|&&x| x % 2 == 0) {
println!("First even: {}", first_even);
}
if let Some(pos) = numbers.iter().position(|&x| x == 5) {
println!("5 at position: {}", pos);
}
// min and max
println!("Min: {:?}", numbers.iter().min());
println!("Max: {:?}", numbers.iter().max());
// chain operations
let result: i32 = numbers.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.take(3)
.sum();
println!("Sum of first 3 even squares: {}", result);
}
5. Vector Capacity and Performance
Understanding Capacity
fn main() {
// Capacity vs length
let mut v = Vec::with_capacity(10);
println!("Initial - len: {}, cap: {}", v.len(), v.capacity());
for i in 0..5 {
v.push(i);
println!("After push {} - len: {}, cap: {}", i, v.len(), v.capacity());
}
// When capacity is exceeded, vector reallocates
for i in 5..15 {
v.push(i);
println!("After push {} - len: {}, cap: {}", i, v.len(), v.capacity());
}
// Shrink to fit
v.shrink_to_fit();
println!("After shrink - len: {}, cap: {}", v.len(), v.capacity());
// Reserve additional capacity
v.reserve(100);
println!("After reserve 100 - cap: {}", v.capacity());
// Reserve exact capacity
v.reserve_exact(50);
println!("After reserve_exact 50 - cap: {}", v.capacity());
}
Performance Comparisons
use std::time::Instant;
fn main() {
// Pre-allocation vs dynamic growth
const SIZE: usize = 100_000;
// Without pre-allocation
let start = Instant::now();
let mut v1 = Vec::new();
for i in 0..SIZE {
v1.push(i);
}
println!("Without pre-allocation: {:?}", start.elapsed());
// With pre-allocation
let start = Instant::now();
let mut v2 = Vec::with_capacity(SIZE);
for i in 0..SIZE {
v2.push(i);
}
println!("With pre-allocation: {:?}", start.elapsed());
// Index access vs get()
let v = vec![0; SIZE];
let start = Instant::now();
for i in 0..SIZE {
let _ = v[i];
}
println!("Direct indexing: {:?}", start.elapsed());
let start = Instant::now();
for i in 0..SIZE {
let _ = v.get(i);
}
println!("get() method: {:?}", start.elapsed());
// Iteration methods
let start = Instant::now();
let mut sum = 0;
for i in 0..SIZE {
sum += v[i];
}
println!("Manual index loop: {:?}", start.elapsed());
let start = Instant::now();
let mut sum = 0;
for &x in &v {
sum += x;
}
println!("Iterator loop: {:?}", start.elapsed());
}
6. Working with Different Types
Vectors of Custom Types
#[derive(Debug, Clone)]
struct Person {
name: String,
age: u32,
}
#[derive(Debug)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn new(x: f64, y: f64) -> Self {
Point { x, y }
}
fn distance(&self, other: &Point) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
(dx * dx + dy * dy).sqrt()
}
}
fn main() {
// Vector of structs
let people = vec![
Person { name: "Alice".to_string(), age: 30 },
Person { name: "Bob".to_string(), age: 25 },
Person { name: "Charlie".to_string(), age: 35 },
];
for person in &people {
println!("{:?}", person);
}
// Vector of Points
let points = vec![
Point::new(0.0, 0.0),
Point::new(3.0, 4.0),
Point::new(1.0, 1.0),
];
let origin = &points[0];
for point in &points[1..] {
println!("Distance from origin: {}", origin.distance(point));
}
// Sorting custom types (need PartialOrd)
let mut people = people;
people.sort_by(|a, b| a.age.cmp(&b.age));
println!("Sorted by age: {:?}", people);
// Vector of enums (heterogeneous data)
#[derive(Debug)]
enum Value {
Int(i32),
Float(f64),
Text(String),
}
let mixed = vec![
Value::Int(42),
Value::Float(3.14),
Value::Text("hello".to_string()),
Value::Int(100),
];
for value in &mixed {
match value {
Value::Int(x) => println!("Int: {}", x),
Value::Float(x) => println!("Float: {}", x),
Value::Text(s) => println!("Text: {}", s),
}
}
}
Vectors of References
fn main() {
// Vector of references
let x = 10;
let y = 20;
let z = 30;
let refs = vec![&x, &y, &z];
for &&val in &refs {
println!("Referenced value: {}", val);
}
// Vector of references to strings
let strings = vec![
String::from("hello"),
String::from("world"),
String::from("!"),
];
let refs: Vec<&String> = strings.iter().collect();
for s in refs {
println!("{}", s);
}
// Vector of boxed values
let boxed: Vec<Box<i32>> = vec![Box::new(1), Box::new(2), Box::new(3)];
for b in &boxed {
println!("Boxed: {}", **b);
}
// Vector of trait objects
trait Animal {
fn speak(&self);
}
struct Dog;
struct Cat;
impl Animal for Dog {
fn speak(&self) {
println!("Woof!");
}
}
impl Animal for Cat {
fn speak(&self) {
println!("Meow!");
}
}
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Dog),
Box::new(Cat),
Box::new(Dog),
];
for animal in &animals {
animal.speak();
}
}
7. Vector Slices and Views
Working with Slices
fn main() {
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Create slices
let slice1 = &v[2..5]; // [3, 4, 5]
let slice2 = &v[..4]; // [1, 2, 3, 4]
let slice3 = &v[6..]; // [7, 8, 9, 10]
let slice4 = &v[..]; // entire vector
println!("slice1: {:?}", slice1);
println!("slice2: {:?}", slice2);
println!("slice3: {:?}", slice3);
// Functions that work with slices
fn sum_slice(slice: &[i32]) -> i32 {
slice.iter().sum()
}
println!("Sum of slice1: {}", sum_slice(slice1));
println!("Sum of v: {}", sum_slice(&v));
// Mutable slices
let mut v = vec![1, 2, 3, 4, 5];
let slice = &mut v[2..4];
slice[0] = 30;
slice[1] = 40;
println!("Modified v: {:?}", v);
// Split into slices
let (left, right) = v.split_at_mut(3);
left[0] = 10;
right[0] = 50;
println!("After split: {:?}", v);
}
Converting Between Vectors and Slices
fn main() {
// Vector to slice
let v = vec![1, 2, 3, 4, 5];
let slice: &[i32] = &v;
// Slice to vector
let arr = [1, 2, 3, 4, 5];
let v_from_slice: Vec<i32> = arr.to_vec();
let v_from_slice2 = Vec::from(&arr[1..4]);
println!("v_from_slice: {:?}", v_from_slice);
println!("v_from_slice2: {:?}", v_from_slice2);
// as_slice and as_mut_slice
let mut v = vec![1, 2, 3];
let s = v.as_slice();
println!("as_slice: {:?}", s);
let s_mut = v.as_mut_slice();
s_mut[0] = 10;
println!("After as_mut_slice: {:?}", v);
// Converting between types
let v: Vec<i32> = (0..5).collect();
let boxed_slice: Box<[i32]> = v.into_boxed_slice();
let v_back: Vec<i32> = boxed_slice.into_vec();
println!("Back to vector: {:?}", v_back);
}
8. Common Vector Operations
Searching and Finding
fn main() {
let v = vec![1, 2, 3, 4, 5, 3, 2, 1];
// Contains
let has_three = v.contains(&3);
println!("Contains 3: {}", has_three);
// Find index of first occurrence
if let Some(pos) = v.iter().position(|&x| x == 3) {
println!("First 3 at position: {}", pos);
}
// Find index of last occurrence
if let Some(pos) = v.iter().rposition(|&x| x == 3) {
println!("Last 3 at position: {}", pos);
}
// Find element
if let Some(&element) = v.iter().find(|&&x| x > 3) {
println!("First element > 3: {}", element);
}
// Binary search (requires sorted vector)
let mut sorted = v.clone();
sorted.sort();
match sorted.binary_search(&3) {
Ok(pos) => println!("3 found at position {} in sorted", pos),
Err(pos) => println!("3 not found, would be at position {}", pos),
}
// Check if all elements satisfy condition
let all_positive = v.iter().all(|&x| x > 0);
println!("All positive: {}", all_positive);
// Check if any element satisfies condition
let any_even = v.iter().any(|&x| x % 2 == 0);
println!("Any even: {}", any_even);
}
Sorting and Ordering
fn main() {
let mut numbers = vec![5, 2, 8, 1, 9, 3, 7, 4, 6];
// Sort in ascending order
numbers.sort();
println!("Ascending: {:?}", numbers);
// Sort in descending order
numbers.sort_by(|a, b| b.cmp(a));
println!("Descending: {:?}", numbers);
// Sort with custom comparator
let mut people = vec![
("Alice", 30),
("Bob", 25),
("Charlie", 35),
("Alice", 20),
];
people.sort_by(|a, b| {
a.0.cmp(&b.0).then(a.1.cmp(&b.1))
});
println!("Sorted people: {:?}", people);
// Partial sort (only sort part of the vector)
let mut v = vec![5, 2, 8, 1, 9, 3, 7, 4, 6];
v.sort_unstable(); // Faster but doesn't preserve order of equal elements
println!("Unstable sort: {:?}", v);
// Reverse
let mut v = vec![1, 2, 3, 4, 5];
v.reverse();
println!("Reversed: {:?}", v);
// Rotate
v.rotate_left(2);
println!("Rotated left by 2: {:?}", v);
v.rotate_right(1);
println!("Rotated right by 1: {:?}", v);
}
Combining Vectors
fn main() {
// Concatenation
let v1 = vec![1, 2, 3];
let v2 = vec![4, 5, 6];
// Method 1: extend
let mut v3 = v1.clone();
v3.extend(v2.clone());
println!("Extended: {:?}", v3);
// Method 2: chain
let v4: Vec<_> = v1.iter().chain(v2.iter()).cloned().collect();
println!("Chained: {:?}", v4);
// Method 3: append (moves)
let mut v5 = v1.clone();
let mut v6 = v2.clone();
v5.append(&mut v6);
println!("Appended: {:?}, v6 empty: {:?}", v5, v6);
// Join (for vectors of strings)
let strings = vec!["hello", "world", "!"];
let joined = strings.join(" ");
println!("Joined: '{}'", joined);
// Split
let numbers = vec![1, 2, 3, 4, 5, 6];
let (first, second): (Vec<_>, Vec<_>) = numbers.iter()
.partition(|&&x| x < 4);
println!("First partition: {:?}, Second: {:?}", first, second);
// Zip
let names = vec!["Alice", "Bob", "Charlie"];
let ages = vec![30, 25, 35];
let zipped: Vec<_> = names.iter().zip(ages.iter()).collect();
println!("Zipped: {:?}", zipped);
}
9. Advanced Vector Techniques
Vector as Stack
fn main() {
let mut stack: Vec<i32> = Vec::new();
// Push onto stack
stack.push(1);
stack.push(2);
stack.push(3);
println!("Stack: {:?}", stack);
// Pop from stack
while let Some(top) = stack.pop() {
println!("Popped: {}", top);
}
// Using as stack with capacity
let mut stack = Vec::with_capacity(5);
for i in 0..5 {
stack.push(i);
println!("Push {}, len: {}, cap: {}", i, stack.len(), stack.capacity());
}
// Peek at top without popping
if let Some(top) = stack.last() {
println!("Top: {}", top);
}
// Stack operations
fn evaluate_postfix(expression: &str) -> Option<i32> {
let mut stack = Vec::new();
for token in expression.split_whitespace() {
match token.parse::<i32>() {
Ok(num) => stack.push(num),
Err(_) => {
let b = stack.pop()?;
let a = stack.pop()?;
match token {
"+" => stack.push(a + b),
"-" => stack.push(a - b),
"*" => stack.push(a * b),
"/" => stack.push(a / b),
_ => return None,
}
}
}
}
stack.pop()
}
let result = evaluate_postfix("3 4 + 2 *");
println!("Postfix result: {:?}", result); // (3+4)*2 = 14
}
Vector as Queue
use std::collections::VecDeque;
fn main() {
// VecDeque is better for queue operations
let mut queue = VecDeque::new();
// Add to back
queue.push_back(1);
queue.push_back(2);
queue.push_back(3);
println!("Queue: {:?}", queue);
// Add to front
queue.push_front(0);
println!("After push_front: {:?}", queue);
// Remove from front
while let Some(front) = queue.pop_front() {
println!("Processing: {}", front);
}
// Using Vec as queue (less efficient)
let mut queue_vec = Vec::new();
queue_vec.push(1);
queue_vec.push(2);
queue_vec.push(3);
while !queue_vec.is_empty() {
let front = queue_vec.remove(0); // O(n) operation!
println!("Processing (Vec): {}", front);
}
// Ring buffer with VecDeque
let mut buffer = VecDeque::with_capacity(5);
for i in 0..10 {
if buffer.len() == 5 {
buffer.pop_front();
}
buffer.push_back(i);
println!("Buffer: {:?}", buffer);
}
}
Matrix Operations with Vectors
#[derive(Debug)]
struct Matrix {
data: Vec<Vec<i32>>,
rows: usize,
cols: usize,
}
impl Matrix {
fn new(rows: usize, cols: usize) -> Self {
Matrix {
data: vec![vec![0; cols]; rows],
rows,
cols,
}
}
fn from_vec(data: Vec<Vec<i32>>) -> Option<Self> {
if data.is_empty() {
return None;
}
let rows = data.len();
let cols = data[0].len();
// Check if all rows have same length
if data.iter().any(|row| row.len() != cols) {
return None;
}
Some(Matrix {
data,
rows,
cols,
})
}
fn get(&self, row: usize, col: usize) -> Option<&i32> {
if row < self.rows && col < self.cols {
Some(&self.data[row][col])
} else {
None
}
}
fn set(&mut self, row: usize, col: usize, value: i32) -> Option<()> {
if row < self.rows && col < self.cols {
self.data[row][col] = value;
Some(())
} else {
None
}
}
fn transpose(&self) -> Matrix {
let mut result = Matrix::new(self.cols, self.rows);
for i in 0..self.rows {
for j in 0..self.cols {
result.data[j][i] = self.data[i][j];
}
}
result
}
fn multiply(&self, other: &Matrix) -> Option<Matrix> {
if self.cols != other.rows {
return None;
}
let mut result = Matrix::new(self.rows, other.cols);
for i in 0..self.rows {
for j in 0..other.cols {
let mut sum = 0;
for k in 0..self.cols {
sum += self.data[i][k] * other.data[k][j];
}
result.data[i][j] = sum;
}
}
Some(result)
}
}
fn main() {
let mut mat = Matrix::new(3, 3);
// Fill matrix
for i in 0..3 {
for j in 0..3 {
mat.set(i, j, (i * 3 + j + 1) as i32).unwrap();
}
}
println!("Original matrix: {:?}", mat);
// Get element
if let Some(val) = mat.get(1, 1) {
println!("Element at (1,1): {}", val);
}
// Transpose
let transposed = mat.transpose();
println!("Transposed: {:?}", transposed);
// Matrix multiplication
let mat2 = Matrix::new(3, 3);
if let Some(product) = mat.multiply(&mat2) {
println!("Product: {:?}", product);
}
}
10. Error Handling with Vectors
Safe Access Patterns
fn main() {
let v = vec![1, 2, 3, 4, 5];
// Safe indexing with match
match v.get(10) {
Some(value) => println!("Value: {}", value),
None => println!("Index out of bounds"),
}
// Safe indexing with if let
if let Some(value) = v.get(2) {
println!("Found: {}", value);
}
// Using unwrap_or for defaults
let value = v.get(10).unwrap_or(&-1);
println!("Default value: {}", value);
// Custom error type
#[derive(Debug)]
enum VecError {
IndexOutOfBounds(usize, usize),
EmptyVector,
}
fn safe_first<T>(v: &[T]) -> Result<&T, VecError> {
v.first().ok_or(VecError::EmptyVector)
}
fn safe_get<T>(v: &[T], index: usize) -> Result<&T, VecError> {
v.get(index).ok_or(VecError::IndexOutOfBounds(index, v.len()))
}
match safe_get(&v, 10) {
Ok(val) => println!("Got: {}", val),
Err(VecError::IndexOutOfBounds(idx, len)) => {
println!("Index {} out of bounds for length {}", idx, len);
}
Err(VecError::EmptyVector) => println!("Vector is empty"),
}
// Try operations
fn try_operations(v: &[i32]) -> Option<i32> {
let first = v.first()?;
let last = v.last()?;
Some(first + last)
}
if let Some(sum) = try_operations(&v) {
println!("Sum of first and last: {}", sum);
}
}
Fallible Vector Operations
use std::num::ParseIntError;
fn main() {
// Collecting Results
let strings = vec!["1", "2", "abc", "3", "def"];
// Collect into Result<Vec<i32>, ParseIntError>
let parsed: Result<Vec<i32>, ParseIntError> = strings
.iter()
.map(|s| s.parse::<i32>())
.collect();
match parsed {
Ok(numbers) => println!("All parsed: {:?}", numbers),
Err(e) => println!("Error parsing: {}", e),
}
// Filter out errors
let successful: Vec<i32> = strings
.iter()
.filter_map(|s| s.parse().ok())
.collect();
println!("Successful: {:?}", successful);
// Partition results
let (successes, failures): (Vec<_>, Vec<_>) = strings
.iter()
.map(|s| s.parse::<i32>())
.partition(Result::is_ok);
let successes: Vec<_> = successes.into_iter().map(Result::unwrap).collect();
let failures: Vec<_> = failures.into_iter().map(Result::unwrap_err).collect();
println!("Successes: {:?}", successes);
println!("Failures: {:?}", failures);
// Try fold for early termination
let numbers = vec![1, 2, 3, 4, 5];
let result = numbers.iter().try_fold(0, |acc, &x| {
if x == 3 {
None
} else {
Some(acc + x)
}
});
match result {
Some(sum) => println!("Sum until 3: {}", sum),
None => println!("Stopped at 3"),
}
}
11. Parallel Processing with Vectors
Using Rayon for Parallel Iteration
// Add rayon = "1.7" to Cargo.toml
use rayon::prelude::*;
fn main() {
let numbers: Vec<i32> = (0..1_000_000).collect();
// Parallel map
let start = std::time::Instant::now();
let squares: Vec<i32> = numbers.par_iter()
.map(|&x| x * x)
.collect();
println!("Parallel map time: {:?}", start.elapsed());
// Parallel filter
let evens: Vec<&i32> = numbers.par_iter()
.filter(|&&x| x % 2 == 0)
.collect();
println!("Found {} evens", evens.len());
// Parallel fold
let sum: i32 = numbers.par_iter()
.fold(|| 0, |acc, &x| acc + x)
.sum();
println!("Parallel sum: {}", sum);
// Parallel any/all
let has_negative = numbers.par_iter().any(|&x| x < 0);
let all_positive = numbers.par_iter().all(|&x| x >= 0);
println!("Has negative: {}, All positive: {}", has_negative, all_positive);
// Parallel find
if let Some(first_even) = numbers.par_iter().find_first(|&&x| x % 2 == 0) {
println!("First even: {}", first_even);
}
// Parallel sort (rayon doesn't have parallel sort, but we can use par_sort)
let mut unsorted: Vec<i32> = (0..100_000).rev().collect();
unsorted.par_sort(); // Uses rayon's parallel sort
println!("First few after sort: {:?}", &unsorted[..5]);
}
12. Common Patterns and Best Practices
Builder Pattern with Vectors
#[derive(Debug, Clone)]
struct QueryBuilder {
select: Vec<String>,
from: String,
where_clauses: Vec<String>,
order_by: Vec<(String, bool)>, // (column, ascending)
limit: Option<usize>,
}
impl QueryBuilder {
fn new(table: &str) -> Self {
QueryBuilder {
select: vec!["*".to_string()],
from: table.to_string(),
where_clauses: Vec::new(),
order_by: Vec::new(),
limit: None,
}
}
fn select(mut self, columns: Vec<&str>) -> Self {
self.select = columns.into_iter().map(String::from).collect();
self
}
fn and_where(mut self, condition: &str) -> Self {
self.where_clauses.push(condition.to_string());
self
}
fn order_by(mut self, column: &str, ascending: bool) -> Self {
self.order_by.push((column.to_string(), ascending));
self
}
fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
fn build(&self) -> String {
let select_str = if self.select.len() == 1 && self.select[0] == "*" {
"*".to_string()
} else {
self.select.join(", ")
};
let mut query = format!("SELECT {} FROM {}", select_str, self.from);
if !self.where_clauses.is_empty() {
query.push_str(" WHERE ");
query.push_str(&self.where_clauses.join(" AND "));
}
if !self.order_by.is_empty() {
let order_str: Vec<String> = self.order_by
.iter()
.map(|(col, asc)| format!("{} {}", col, if *asc { "ASC" } else { "DESC" }))
.collect();
query.push_str(" ORDER BY ");
query.push_str(&order_str.join(", "));
}
if let Some(limit) = self.limit {
query.push_str(&format!(" LIMIT {}", limit));
}
query
}
}
fn main() {
let query = QueryBuilder::new("users")
.select(vec!["id", "name", "email"])
.and_where("age > 18")
.and_where("active = true")
.order_by("name", true)
.limit(10)
.build();
println!("{}", query);
}
Pool Pattern with Vectors
struct ObjectPool<T> {
objects: Vec<T>,
max_size: usize,
}
impl<T> ObjectPool<T> {
fn new(max_size: usize) -> Self {
ObjectPool {
objects: Vec::with_capacity(max_size),
max_size,
}
}
fn acquire(&mut self) -> Option<T> {
self.objects.pop()
}
fn release(&mut self, obj: T) -> Result<(), T> {
if self.objects.len() < self.max_size {
self.objects.push(obj);
Ok(())
} else {
Err(obj) // Pool full
}
}
fn with_capacity(capacity: usize, f: impl Fn() -> T) -> Self {
let mut objects = Vec::with_capacity(capacity);
for _ in 0..capacity {
objects.push(f());
}
ObjectPool {
objects,
max_size: capacity,
}
}
}
#[derive(Debug)]
struct Connection {
id: usize,
connected: bool,
}
impl Connection {
fn new(id: usize) -> Self {
Connection {
id,
connected: true,
}
}
fn query(&self, sql: &str) {
println!("Connection {} executing: {}", self.id, sql);
}
}
fn main() {
let mut pool = ObjectPool::with_capacity(3, || Connection::new(rand::random()));
// Acquire connections
if let Some(mut conn) = pool.acquire() {
conn.query("SELECT * FROM users");
// Release back to pool
let _ = pool.release(conn);
}
// Pool full example
for i in 0..5 {
let conn = Connection::new(i);
match pool.release(conn) {
Ok(()) => println!("Connection {} added to pool", i),
Err(conn) => println!("Pool full, connection {} dropped", conn.id),
}
}
}
13. Testing and Documentation
Testing Vector Operations
#[cfg(test)]
mod tests {
#[test]
fn test_vector_creation() {
let v1 = vec![1, 2, 3];
assert_eq!(v1.len(), 3);
assert_eq!(v1[0], 1);
let v2: Vec<i32> = (0..5).collect();
assert_eq!(v2, vec![0, 1, 2, 3, 4]);
}
#[test]
fn test_vector_operations() {
let mut v = vec![1, 2, 3];
v.push(4);
assert_eq!(v, vec![1, 2, 3, 4]);
let last = v.pop();
assert_eq!(last, Some(4));
assert_eq!(v, vec![1, 2, 3]);
v.insert(1, 10);
assert_eq!(v, vec![1, 10, 2, 3]);
}
#[test]
fn test_vector_iteration() {
let v = vec![1, 2, 3];
let sum: i32 = v.iter().sum();
assert_eq!(sum, 6);
let doubled: Vec<i32> = v.iter().map(|&x| x * 2).collect();
assert_eq!(doubled, vec![2, 4, 6]);
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_out_of_bounds() {
let v = vec![1, 2, 3];
let _ = v[5]; // This panics
}
#[test]
fn test_safe_access() {
let v = vec![1, 2, 3];
assert_eq!(v.get(5), None);
assert_eq!(v.get(1), Some(&2));
}
}
Documentation Examples
/// A custom vector wrapper with additional functionality
///
/// # Examples
///
/// ```
/// use mylib::MyVec;
///
/// let mut v = MyVec::new();
/// v.push(1);
/// v.push(2);
/// v.push(3);
///
/// assert_eq!(v.sum(), 6);
/// assert_eq!(v.average(), 2.0);
/// ```
#[derive(Debug, Clone)]
pub struct MyVec<T> {
data: Vec<T>,
}
impl<T> MyVec<T> {
/// Creates a new empty `MyVec`
pub fn new() -> Self {
MyVec { data: Vec::new() }
}
/// Adds an element to the vector
///
/// # Examples
///
/// ```
/// let mut v = MyVec::new();
/// v.push(42);
/// assert_eq!(v.len(), 1);
/// ```
pub fn push(&mut self, value: T) {
self.data.push(value);
}
/// Returns the number of elements
pub fn len(&self) -> usize {
self.data.len()
}
/// Returns true if the vector is empty
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
impl MyVec<i32> {
/// Returns the sum of all elements
///
/// # Panics
///
/// Panics if the vector is empty
pub fn sum(&self) -> i32 {
self.data.iter().sum()
}
/// Returns the average of all elements
///
/// # Panics
///
/// Panics if the vector is empty
pub fn average(&self) -> f64 {
self.sum() as f64 / self.len() as f64
}
}
impl<T> Default for MyVec<T> {
fn default() -> Self {
Self::new()
}
}
Conclusion
Vectors are one of the most versatile and commonly used collections in Rust:
Key Takeaways
- Dynamic sizing: Vectors grow and shrink at runtime
- Memory efficient: Elements stored contiguously
- Fast access: O(1) index access
- Ownership: Vector owns its elements
- Rich API: Extensive set of methods for manipulation
- Performance: Control over capacity for optimization
Common Operations Summary
| Operation | Method | Time Complexity |
|---|---|---|
| Create empty | Vec::new() | O(1) |
| Create with values | vec![1, 2, 3] | O(n) |
| Add to end | push() | O(1) amortized |
| Remove from end | pop() | O(1) |
| Insert at index | insert() | O(n) |
| Remove at index | remove() | O(n) |
| Get element | get() | O(1) |
| Iterate | iter() | O(n) |
| Find element | position() | O(n) |
| Sort | sort() | O(n log n) |
Best Practices
- Pre-allocate capacity when you know the size in advance
- Use
get()for safe indexing instead of direct indexing - Prefer iterators over manual indexing when possible
- Use
Vec::with_capacity()for performance-critical code - Consider
VecDequeif you need to add/remove from both ends - Use
shrink_to_fit()to free unused memory - Be mindful of ownership when passing vectors to functions
Vectors are fundamental to Rust programming and mastering them is essential for writing efficient and idiomatic Rust code.