Academy
Vec Mastery: The Dynamic Array That Powers 90% of Rust Programs
Master Rust's most vital collection, Vec. Learn creation methods, safe access, iterators, slices, and optimization tips from RantAI's guide.
6 menit baca
Di halaman ini
- Creating Vectors: Multiple Paths to the Same Destination
- Accessing Elements: Safe and Unsafe Options
- Iteration: The Idiomatic Way to Process Elements
- Slices: Borrowing Parts of a Vector
- Common Operations: The Essentials
- Broader Implications: Performance Through Simplicity
- Practical Applications & Strategic Takeaways
- Our Commitment to Open Knowledge
- Support Our Mission & Get Your Handbook
If there were a "most valuable type" award in Rust, Vec<T> would win it every year by a landslide—and the acceptance speech would just be vec!["thank", "you"]. It's the first collection most developers learn, the default container for "I need a list of things," and the type that appears in virtually every Rust program ever written, from weekend hobby projects to production systems handling millions of requests per second.
Vec<T> is a growable, heap-allocated array. It stores elements contiguously in memory (great for cache performance), grows dynamically when you add elements (no fixed-size limitations), and integrates seamlessly with Rust's ownership system, iterators, and slice operations. If you need a collection and don't have a specific reason to use something else, Vec is the answer. This isn't laziness—it's engineering pragmatism. Contiguous memory access patterns are so fast on modern hardware that Vec outperforms theoretically superior data structures in practice for most workloads.
At RantAI, Vec is the workhorse of our data pipelines. Sensor readings, processing stages, batch results, token sequences—they all live in Vec because it's fast, predictable, and works naturally with everything else in the Rust ecosystem. This article, drawn from Chapter 4, Section 4.4.1 of our guide "The Rust Programming Language," covers everything you need to master the type you'll use more than any other.
Creating Vectors: Multiple Paths to the Same Destination#
fn main() {
// The macro way — most common for known values
let numbers = vec![1, 2, 3, 4, 5];
// The explicit way — when building dynamically
let mut dynamic: Vec<String> = Vec::new();
dynamic.push("first".to_string());
dynamic.push("second".to_string());
// Pre-allocated — when you know the approximate size
let mut preallocated: Vec<i32> = Vec::with_capacity(1000);
// No reallocations needed until you exceed 1000 elements
// Initialized with a default value
let zeros = vec![0.0_f64; 100]; // 100 zeros
println!("{:?}", numbers); // [1, 2, 3, 4, 5]
println!("{:?}", dynamic); // ["first", "second"]
println!("Capacity: {}", preallocated.capacity()); // 1000
}
Vec::with_capacity is worth highlighting: if you know roughly how many elements you'll add, pre-allocating avoids the overhead of repeated reallocations as the vector grows. Each reallocation copies the entire contents to a new, larger buffer. For performance-sensitive code, pre-allocating is a simple optimization that can make a meaningful difference.
Accessing Elements: Safe and Unsafe Options#
fn main() {
let v = vec![10, 20, 30, 40, 50];
// Indexing — panics on out-of-bounds
let third = v[2]; // 30
// let bad = v[10]; // PANIC at runtime!
// .get() — returns Option, never panics
match v.get(2) {
Some(val) => println!("Third element: {}", val), // 30
None => println!("Index out of bounds"),
}
// First and last
println!("First: {:?}", v.first()); // Some(10)
println!("Last: {:?}", v.last()); // Some(50)
}
The choice between v[i] and v.get(i) is a design decision. Use v[i] when you're certain the index is valid and an out-of-bounds access would be a bug worth panicking over. Use v.get(i) when the index might be invalid and you want to handle it gracefully. The compiler doesn't force either pattern—it trusts you to choose based on context.
Iteration: The Idiomatic Way to Process Elements#
fn main() {
let names = vec!["Alice", "Bob", "Charlie"];
// Immutable iteration — most common
for name in &names {
println!("Hello, {}!", name);
}
// Mutable iteration — modify in place
let mut scores = vec![85, 92, 78, 95, 88];
for score in &mut scores {
*score += 5; // Curve everyone's grade
}
println!("Curved: {:?}", scores); // [90, 97, 83, 100, 93]
// Consuming iteration — takes ownership
let data = vec![1, 2, 3];
let doubled: Vec<i32> = data.into_iter().map(|x| x * 2).collect();
// data is consumed — can't use it anymore
println!("Doubled: {:?}", doubled); // [2, 4, 6]
}
The three iteration modes mirror Rust's ownership system: &v borrows immutably (multiple readers), &mut v borrows mutably (one writer), and v.into_iter() consumes the vector (takes ownership). The same mental model that governs ownership governs iteration. Once you internalize this, it becomes second nature.
Slices: Borrowing Parts of a Vector#
fn average(data: &[f64]) -> f64 {
let sum: f64 = data.iter().sum();
sum / data.len() as f64
}
fn main() {
let values = vec![10.0, 20.0, 30.0, 40.0, 50.0];
println!("Full average: {:.1}", average(&values)); // 30.0
println!("First three: {:.1}", average(&values[..3])); // 20.0
println!("Last two: {:.1}", average(&values[3..])); // 45.0
}
By accepting &[f64] instead of &Vec<f64>, the average function works with any contiguous slice of floats—vectors, arrays, or subslices. This is idiomatic Rust: accept the most general type that works, which gives callers maximum flexibility.
Common Operations: The Essentials#
fn main() {
let mut v = vec![3, 1, 4, 1, 5, 9, 2, 6];
v.sort(); // [1, 1, 2, 3, 4, 5, 6, 9]
v.dedup(); // [1, 2, 3, 4, 5, 6, 9] — removes consecutive duplicates
v.retain(|&x| x > 3); // [4, 5, 6, 9] — keeps only elements matching predicate
v.reverse(); // [9, 6, 5, 4]
println!("Contains 5: {}", v.contains(&5)); // true
println!("Length: {}", v.len()); // 4
println!("Is empty: {}", v.is_empty()); // false
// Extend from another collection
v.extend([10, 11, 12]);
println!("{:?}", v); // [9, 6, 5, 4, 10, 11, 12]
}
Broader Implications: Performance Through Simplicity#
Vec is fast because contiguous memory is fast. Modern CPUs are optimized for sequential memory access—prefetching, cache lines, and SIMD instructions all work best when data is stored contiguously. A Vec<i32> with a million elements fits neatly into cache-friendly access patterns. A linked list with a million elements scatters nodes across the heap, and your CPU's prefetcher weeps.
At RantAI, we default to Vec for everything unless profiling shows a specific reason to use a different data structure. In our experience, Vec with binary search often outperforms HashMap for small-to-medium lookup tables, Vec with .retain() outperforms HashSet for small membership tests, and Vec with .sort().dedup() is often the simplest way to get unique elements. Start with Vec. Optimize later if needed. You'll be surprised how often "later" never comes.
Practical Applications & Strategic Takeaways#
For newcomers: Vec is your default container. Use vec![] for known data, Vec::new() for building dynamically, and Vec::with_capacity() when you know the approximate size. Accept &[T] in function parameters for maximum flexibility.
For performance engineers: Pre-allocate with Vec::with_capacity() in hot paths. Avoid repeated push without pre-allocation on large vectors—each reallocation copies the entire buffer. Use Vec::drain() for efficient partial consumption.
For architects: Design your APIs around slices (&[T]) rather than Vec<T>. This decouples your logic from the specific container type and makes functions work with arrays, vectors, and subslices equally.
Our Commitment to Open Knowledge#
RantAI is committed to open education. Vec is covered in Chapter 4, Section 4.4.1 of our guide, "The Rust Programming Language," freely available online.
Explore these concepts further: https://trpl.rantai.dev
Support Our Mission & Get Your Handbook#
Get the Handbook on Amazon KDP: https://www.amazon.com/dp/B0DHCMD3F2
Get the Handbook on Google Play Books: https://play.google.com/store/books/details?id=INwfEQAAQBAJ
What's the most creative thing you've done with a Vec? Or the performance surprise that made you appreciate contiguous memory? Share below!
#RustLang #Vec #Collections #Performance #RantAI #LearnRust #DataStructures #SystemsProgramming #CachePerformance #Iterators
Artikel berikutnya
HashMap: Fast Lookups, Smart Insertions, and the Entry API That Changes Everything