Collections
Work with Rust's essential collections: Vec, HashMap, String, and iterators.
Vectors
A Vec is a growable, heap-allocated array that can store elements of the same type. Vectors are the most commonly used collection in Rust and provide methods for adding, removing, and accessing elements. You can create a vector with Vec::new or the vec! macro. Accessing elements by index can panic if the index is out of bounds, but the get method returns an Option that lets you handle the missing case safely.
fn main() {
// Creating vectors
let mut numbers: Vec<i32> = Vec::new();
numbers.push(10);
numbers.push(20);
numbers.push(30);
let colors = vec!["red", "green", "blue"];
// Accessing elements
println!("First number: {}", numbers[0]);
println!("Safe access: {:?}", numbers.get(99));
// Iterating
for num in &numbers {
print!("{} ", num);
}
println!();
// Useful methods
numbers.sort();
numbers.reverse();
println!("Contains 20: {}", numbers.contains(&20));
println!("Length: {}", numbers.len());
// Functional operations
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
println!("Doubled: {:?}", doubled);
let sum: i32 = numbers.iter().sum();
println!("Sum: {}", sum);
// Retain only even numbers
let mut vals = vec![1, 2, 3, 4, 5, 6, 7, 8];
vals.retain(|x| x % 2 == 0);
println!("Evens: {:?}", vals);
}
HashMap
HashMap stores key-value pairs and provides average O(1) lookup time. Keys must implement the Eq and Hash traits, which most standard types already do. You can insert entries, look up values by key, update existing entries, and iterate over all key-value pairs. The entry API provides an elegant way to insert a value only if the key does not already exist, which is useful for counting and grouping operations.
use std::collections::HashMap;
fn main() {
let mut scores: HashMap<String, i32> = HashMap::new();
// Inserting
scores.insert("Alice".to_string(), 95);
scores.insert("Bob".to_string(), 87);
scores.insert("Charlie".to_string(), 92);
// Accessing
if let Some(score) = scores.get("Alice") {
println!("Alice's score: {}", score);
}
// Entry API - insert if absent
scores.entry("Diana".to_string()).or_insert(88);
scores.entry("Alice".to_string()).or_insert(0); // won't overwrite
// Iterating
for (name, score) in &scores {
println!("{}: {}", name, score);
}
// Word frequency counter
let text = "the cat sat on the mat the cat";
let mut word_count: HashMap<&str, i32> = HashMap::new();
for word in text.split_whitespace() {
let count = word_count.entry(word).or_insert(0);
*count += 1;
}
println!("
Word frequencies:");
for (word, count) in &word_count {
println!(" '{}': {}", word, count);
}
}
Strings
Rust has two main string types: String, which is a growable heap-allocated UTF-8 string, and &str, which is a string slice that references a portion of string data. String owns its data while &str borrows it. Concatenation can be done with the push_str method, the + operator, or the format! macro. Because Rust strings are UTF-8 encoded, indexing by byte position is not directly supported since a single character can span multiple bytes.
fn main() {
// Creating strings
let mut greeting = String::from("Hello");
greeting.push_str(", World");
greeting.push('!');
println!("{}", greeting);
// String formatting
let name = "Rust";
let version = 2024;
let formatted = format!("{} v{}", name, version);
println!("{}", formatted);
// String methods
let sentence = " Hello, Rust Programming! ";
println!("Trimmed: '{}'", sentence.trim());
println!("Uppercase: {}", sentence.to_uppercase());
println!("Contains 'Rust': {}", sentence.contains("Rust"));
println!("Replace: {}", sentence.replace("Rust", "Systems"));
// Splitting and collecting
let csv = "apple,banana,cherry,date";
let fruits: Vec<&str> = csv.split(',').collect();
println!("Fruits: {:?}", fruits);
// Iterating over characters
let word = "hello";
let capitalized: String = word
.chars()
.enumerate()
.map(|(i, c)| {
if i == 0 { c.to_uppercase().next().unwrap() } else { c }
})
.collect();
println!("Capitalized: {}", capitalized);
}
Iterators
Iterators are a central abstraction in Rust that provide a lazy, composable way to process sequences of values. The Iterator trait requires implementing the next method which returns Option to signal when the sequence is exhausted. Iterator adaptors like map, filter, and take transform iterators without consuming them. Collecting or calling a consuming adaptor like sum or count triggers the actual computation. Iterators in Rust are zero-cost abstractions that compile down to code as efficient as hand-written loops.
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Chaining iterator operations
let result: Vec<i32> = numbers
.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.collect();
println!("Even squares: {:?}", result);
// fold (reduce)
let product: i32 = numbers.iter().fold(1, |acc, &x| acc * x);
println!("Product of 1..10: {}", product);
// zip combines two iterators
let names = vec!["Alice", "Bob", "Charlie"];
let ages = vec![30, 25, 35];
let people: Vec<_> = names.iter().zip(ages.iter()).collect();
println!("People: {:?}", people);
// enumerate gives index and value
for (i, name) in names.iter().enumerate() {
println!(" {}: {}", i, name);
}
// Chaining and flattening
let nested = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
let flat: Vec<&i32> = nested.iter().flat_map(|v| v.iter()).collect();
println!("Flattened: {:?}", flat);
// any, all, find
let has_negative = numbers.iter().any(|&x| x < 0);
let all_positive = numbers.iter().all(|&x| x > 0);
let first_gt_5 = numbers.iter().find(|&&x| x > 5);
println!("Has negative: {}", has_negative);
println!("All positive: {}", all_positive);
println!("First > 5: {:?}", first_gt_5);
}