Containers
Rust implements a number of container types, similar to C++ standard library types.
Arrays
An array in Rust can be declared as follows:
fn main() { let my_array = [1, 2, 3, 4]; // Type is inferred let my_array: [u32; 4] = [1, 2, 3, 4]; // Type specified }
Just like C and C++, arrays are stored on the stack. Similar C++:
#include <array>
int main() {
int my_array[4] = {1, 2, 3, 4};
std::array<int, 4> my_array_std = {1, 2, 3, 4};
return 0;
}
Unlike C, Rust stores the length of the array and bounds-checks accesses. Accessing my_array[5]
will panic, rather than exposing memory locations.
#include <stdio.h>
int main() {
int my_array[4] = {1, 2, 3, 4};
printf("%d", my_array[5]);
return 0;
}
Prints "32767" on my test system. The equivalent Rust fails to compile, but we can fool it by adding a little arithmetic:
fn main() { let array = [1, 2, 3, 4]; for index in 2..6 { println!("{}", array[index]); } }
This panics with the error message:
thread 'main' panicked at src/main.rs:4:24:
index out of bounds: the len is 4 but the index is 4
This is good, safe behavior. (A get_unchecked
call exists, and requries an unsafe
block, to elide the bounds checking).
Vectors
C programmers sometimes complain that in Rust, everything looks like a vector. They aren't wrong: vectors are everywhere. C++ programmers tend to have vectors everywhere too!
A vector is like an array, but: it can grow, and it is stored on the heap. A C++ vector is typically a pointer to an area of heap memory, a notation about size and capacity, and a notation about the type stored inside. Rust vectors are exactly the same, including the same growth characteristics: when the capacity is exhausted, they double in size.
Let's add some data to a vector and debug-print it:
fn main() { let mut my_vec = Vec::new(); my_vec.push(1); my_vec.push(2); println!("{my_vec:?}"); }
This is the same as the C++:
#include <stdio.h>
#include <vector>
int main() {
std::vector<int> my_vec;
my_vec.push_back(1);
my_vec.push_back(2);
for (auto val : my_vec) {
printf("%d", val);
}
return 0;
}
Rust is safe by default on bounds-checking:
fn main() { let my_vec = vec![1, 2]; // Helpful macro for initializing vectors println!("{}", my_vec[3]); }
Panics with an out-of-bounds error. The direct-equivalent C++:
#include <stdio.h>
#include <vector>
int main() {
std::vector<int> my_vec;
my_vec.push_back(1);
my_vec.push_back(2);
printf("%d", my_vec[3]);
return 0;
}
Prints "0" and terminates normally on my system. (You can use at
in C++ for a safe version; C++ typically defaults to unsafe, Rust to safe). Just like an array, get_unchecked
is available for unsafe access (with an unsafe
tag) if you really need to skip the bounds check.
If you need to pre-allocate a vector, you can use with_capacity
:
fn main() { let mut my_vec = Vec::with_capacity(100); my_vec.push(1); }
with_capacity
generates an empty vector, but with pre-allocated capacity for the number of elements you want to store.
HashMap
Rust also includes a HashMap
. It doesn't offer any ordering guarantees, and is comparable to std::unordered_map
. It implements "HashBrown" as the default hashing mechanism, which while fast is cryptographically safe---sometimes for performance people use FxHash
instead.
use std::collections::HashMap; fn main() { let mut my_map = HashMap::new(); my_map.insert("Hello".to_string(), 5); my_map.insert("World".to_string(), 6); if let Some(count) = my_map.get("Hello") { println!("{count}"); } }
Other Types
Rust implements many other containers:
VecDeque
- a vector that acts like a queue, similar todeque
in C++.LinkedList
- a premade linked-list type (writing linked lists in Rust is notoriously hard)BTreeMap
- a binary tree map that retains order.HashSet
- a direct equivalent tounordered_set
.BTreeSet
- a set implemented with a binary tree.BinaryHeap
- a heap structure.
There are many more available through the crates infrastructure, which we'll cover when we get to dependencies.