So, you’ve dipped your toes into Rust—perhaps by watching a few YouTube tutorials, reading a book, or experimenting with some code. But now, as you start coding, you find yourself constantly Googling or searching into StackOverflow for basic code. This ever-growing Rust cheat sheet is designed to save you time by compiling common code snippets for everyday Rust programming. Hopefully, it will save you time as well. Let's begin!
Check the data type of a variable
Rust provides a function std::any::type_name::<T>()
that returns the name of the type as a string.
use std::any::type_name;
fn type_of<T>(_: &T) -> &'static str {
type_name::<T>()
}
let s = String::from("Hello, World!");
let x = s.chars().nth(0);
println!("x is of type: {}", type_of(&x));
//x is of type: core::option::Option<char>
Minimum and Maximum
Use std::cmp::min(a,b) to compare two values a and b. This function works with any type that implements the Ord trait, such as integer, float and other comparable types. Similarly cmp::max() works.
For float-point numbers f32 or f64, you can use it’s function min.
let min = std::cmp::min(a,b);
let max = std::cmp::max(a,b);
a.min(b);
a.max(b);
The cmp method in Rust returns an ordering result which can be Less, Equal or Greater.
//Signature of cmp
fn cmp(&self, other: &Self) -> Ordering
let comparison = a.cmp(&b);
match comparison {
std::cmp::Ordering::Less => println!("a is less than b"),
std::cmp::Ordering::Equal => println!("a is equal to b"),
std::cmp::Ordering::Greater => println!("a is greater than b"),
}
Vector
A Vec<T>
in Rust is a growable, heap-allocated, contiguous sequence of elements of type T
.
let mut v: Vec<i32> = Vec::new(); //Empty Vector
let v = vec![1, 2, 3]; //Using the vec! Macro
v.retain(|&x| x % 2 == 0); // Keeps only even numbers
//Remove all instances of a value in place
v.retain(|&x| x != val);
//Iterating Over Vectors
for val in &v { //Iterating by Reference
println!("{}", val);
}
for val in &mut v { //Iterating by Mutable Reference
*val *= 2;
}
for val in v { //Consuming the Vector
println!("{}", val);
}
// v cannot be used after this point
let v1 = vec![1, 2, 3];
let v2 = v1; // v1 is moved to v2
// v1 is no longer accessible
//Only one mutable reference or any number of immutable references.
Minimum, Maximum and Sum of a Vector
Use the iter()
method combined with the max()
and min()
functions, min()
and max()
are methods from the Iterator
trait that return Option<&T>
. The result will be Some(value)
if the vector is non-empty, or None
if the vector is empty. The values need to be unwrapped using pattern matching.
let mut v = vec![80, 50, 10, 20, 45, 30];
match v.iter().min() {
Some(min_value) => println!("Minimum value: {}", min_value),
None => println!("The vector is empty!"),
}
// Using let Some
if let Some(max_value) = v.iter().max() {
println!("Maximum value: {}", max_value);
}
// Sum the elements using sum()
let total: i32 = v.iter().sum();
let total: i32 = v.iter().fold(0, |acc, &x| acc + x);
Slicing of Vector
Let’s say you want to iterate over a vector starting from second element (i.e., excluding the first element):
let mut v = vec![80, 50, 10, 20, 45, 30];
//Using Slice of vector
for i in &v[1..] {
println!("{}", i);
}
//Using Mutable Slices for Modification
for i in &mut v[1..] {
*i += 5; // To modify each element dereference i with *i
}
//Using Index-Based Looping
for idx in 1..v.len() {
println!("Index: {}, Value: {}", idx, v[idx]);
}
//Using Enumerate with Skip
for (idx, val) in v.iter().enumerate().skip(1) {
println!("Index: {}, Value: {}", idx, val);
}
&v[1..]
: This creates an immutable slice of v
starting from index 1
to the end.
Sorting
The sort()
method sorts the elements of the vector in place in ascending order. The elements must implement the Ord
trait.
If you want to sort with a custom comparator, you can use the sort_by()
method, which takes a closure to determine the order of elements.
v.sort(); // Sorts in ascending order
v.sort_by(|a, b| b.cmp(a)); // Sorting in descending order
Let’s say you want to sort vector of vectors based on some conditions such as the first element of each inner vector, or sum of elements etc., you can use sort_by() method. Here are a few examples:
// Sorts by the first element
vec_of_vecs.sort_by(|a, b| a[0].cmp(&b[0]));
// Sorts by the length of inner vectors
vec_of_vecs.sort_by(|a, b| a.len().cmp(&b.len()));
// Sorts by sum of elements
vec_of_vecs.sort_by(|a, b| a.iter().sum::<i32>().cmp(&b.iter().sum::<i32>()));
HashMap
The HashMap
in Rust is a key-value store from the std::collections
module. You can use the get()
method, which returns an Option
.
let mut m:HashMap<i32,i32> = HashMap::new(); //Declaring a HashMap
m.insert(1,1); //Inserting Values into a HashMap
m.insert(2,2);
let n = 2;
//Accessing Values from a HashMap using match
match m.get(&n){
Some(value) => {println!("Value for 'n': {}", *value);}
None => { //Key does not exist
let value = 2;
m.insert(n,value);
}
}
//Accessing Values using if let
if let Some(value) = m.get(2) {
println!("Value for 2: {}", value);
} else {
println!("Key not found");
}
//Using contains_key
if m.contains_key(2) {
println!("Key exists");
} else {
println!("Key does not exist");
}
// Removing Values from a HashMap
let removed_value = m.remove(2);
println!("{:?}", removed_value);
Find the key with the maximum value
You can use the iter()
method to iterate over the key-value pairs and use the max_by_key()
method to find the pair with the highest value.
// Find the key with the maximum value
if let Some((k, v)) = m.iter().max_by_key(|entry| entry.1) {
println!("max key and maximum value is: {}: {}", k, v);
} else {
println!("The HashMap is empty.");
}
max_by_key(|entry| entry.1)
finds the key-value pair with the maximum value. Here, entry
is a tuple (&key, &value)
, and entry.1
accesses the value.
.entry()
It returns an Entry
enum, which represents either a vacant entry (Vacant
) if the key is not present or an occupied entry (Occupied
) if the key is already present. or_insert(value)
inserts value
into the map if the key is not already present and returns a mutable reference to the value in the map (either the existing one or the newly inserted one).
let mut map = HashMap::new();
// Access the entry for the key 'a'
map.entry('a').or_insert(0);
// Now the map contains {'a': 0}
let count = map.entry('a').or_insert(0);
*count += 1; // Increment the count for 'a'
.get_mut()
get_mut()
retrieves a mutable reference to a value in a HashMap
. It returns an Option<&mut V>
, so you can modify the value if the key exists (Some(&mut value)
).
let mut m: HashMap<i32, Vec<i32>> = HashMap::new();
// Initialize the HashMap with empty vectors
for i in 0..5 {
m.insert(i, Vec::new());
}
//my_vec: Vec<Vec<i32>>
for i in &my_vec {
if let Some(t) = m.get_mut(&i[0]) { //get_mut() retrieves a mutable reference
t.push(i[1]);
}
}
BinaryHeap
BinaryHeap
is a max-heap, meaning the largest element is always at the top.
Heap Operations:
push(value)
: Inserts a value into the heap.pop()
: Removes and returns the greatest value (for max-heap).peek()
: Returns a reference to the greatest value without removing it.
let mut heap = BinaryHeap::new();
// Insert elements into the heap
heap.push(5);
let vec = vec![5, 1, 10, 3];
let heap2 = BinaryHeap::from(vec); //vec is no longer usable after this
//Using collect with an Iterator
let heap3: BinaryHeap<_> = vec.into_iter().collect();
Finding the Kth Largest Element
fn find_kth_largest(nums: Vec<i32>, k: usize) -> i32 {
let mut heap = BinaryHeap::from(nums);
for _ in 0..k - 1 {
heap.pop();
}
heap.pop().unwrap()
}
HashSet
A HashSet<T>
is an unordered collection of unique values of type T
, implemented as a hash table.
let mut set: HashSet<i32> = HashSet::new();
let vec = vec![1, 2, 3, 4];
let set: HashSet<_> = vec.into_iter().collect();
//Using the From Trait
let set = HashSet::from([1, 2, 3, 4]);
//adding elements
set.insert(77);
//Removing Elements
set.remove(&77); //Returns true if the element was present.
//Checking for Elements
if set.contains(&77) {
println!("Set contains 77.");
}
Set Operations
let set_a = HashSet::from([1, 2, 3]);
let set_b = HashSet::from([3, 4, 5]);
//Returns an iterator
let union: HashSet<_> = set_a.union(&set_b).cloned().collect();
let intersection: HashSet<_> = set_a.intersection(&set_b).cloned().collect();
let difference: HashSet<_> = set_a.difference(&set_b).cloned().collect();
//Returns an iterator over elements that are in either set_a or set_b but not in both.
let sym_diff: HashSet<_> = set_a.symmetric_difference(&set_b).cloned().collect();
//Subset and Superset Checks
if set_a.is_subset(&set_b) {
println!("set_a is a subset of set_b");
}
if set_a.is_superset(&set_b) {
println!("set_a is a superset of set_b");
}
Convert a HashSet
to a Vec
A HashSet
is unordered, so when you collect its elements into a Vec
, the order in the resulting vector is arbitrary. This is straightforward using the iter()
method and the collect()
function.
.cloned()
creates an iterator that yields owned copies of the elements.
let vec: Vec<i32> = hash_set.iter().cloned().collect();
//Using into_iter()
//This moves the elements out of the HashSet and into the vector.
let vec: Vec<i32> = hash_set.into_iter().collect();
String
The Rust String and str types represent text using the UTF-8 encoding form, which means they can contain characters that are more than one byte in size. Because of this, you cannot directly index into a String
or &str
using the square bracket syntax like s[1]
or s[2]
.
Access Characters at a Specific Position
The chars()
method returns an iterator over the characters (char
type) of a string. You can use the nth()
method to get the character at a specific index. nth()
returns an Option<char>
, so you need to handle the case where the index is out of bounds. For example, nth(1)
retrieves the character at index 1 (second character). This method accounts for multi-byte characters and is safe for UTF-8 strings. The time complexity of nth()
is O(n), as it iterates through the string up to the nth character.
let s = String::from("Hello");
let c1 = s.chars().nth(1);
let c2 = s.chars().nth(2);
You can collect the characters into a vector and then index directly. If you need frequent random access the can prefer this method.
let s = String::from("Hello");
let chars: Vec<char> = s.chars().collect();
let c1 = chars[1];
Remove Spaces from a String
let s = String::from("Hello World!");
//Using the replace Method
let s_no_spaces = s.replace(" ", "");
You can iterate over each character, filter out the spaces, and collect the result into a new String
.
let s1: String = s.chars().filter(|&c| c != ' ').collect();
let s2: String = s.chars().filter(|c| !c.is_whitespace()).collect();
//Using the retain Method
s.retain(|c| c != ' ');
Methods that consume the string (like replace
and filter
) create a new String
, so you need to assign the result to a new variable.
The is_whitespace()
method checks for Unicode whitespace characters, making it robust for international text. Using retain
modifies the string in place and can be more efficient since it doesn't create an intermediate collection.
Transforming Strings: Lowercase Conversion and Filtering
let mut s = String::from("Hello, World! 123");
s.retain(|c| c.is_alphanumeric());
s = s.to_ascii_lowercase();
You can iterate over each character in the string, convert it to lowercase, filter out non-alphanumeric characters, and collect the result into a new String
.
let s = String::from("Hello, World! 123");
let cleaned: String = s
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
Split a String by Spaces
The split_whitespace()
method splits a string slice (&str
) on Unicode whitespace characters and returns an iterator over the substrings. The type Vec<&str>
means the vector contains string slices referencing the original string s
. split_whitespace()
automatically ignores leading and trailing whitespace. split_whitespace()
splits on any Unicode whitespace character, including spaces, tabs (\t
), newlines (\n
), and carriage returns (\r
).
let words: Vec<&str> = s.split_whitespace().collect();
//If You Need Vec<String>
let words: Vec<String> = s
.split_whitespace()
.map(|s| s.to_string())
.collect();
let words: Vec<&str> = s.split(' ').collect();
split()
method can be used with a space delimiter. But consecutive spaces result in empty strings (""
) in the output. split(' ')
will include empty strings.
let words: Vec<&str> = s.split(' ').collect();
//To filter out empty strings
let words: Vec<&str> = s
.split(' ')
.filter(|&s| !s.is_empty())
.collect();
Joining a Vector of Strings
You can use the join()
method on a Vec<String>
or a Vec<&str>
. This method concatenates the elements of the vector, placing a separator between them.
let words = vec!["Hello", "world"];
let sentence = words.join(" ");
words[1] = "Rust";
// The sentence remains unchanged
println!("{}", sentence); // Output: Hello world
// Print the modified vector
println!("{:?}", words); // Output: ["Hello", "Rust"]
The join()
method does not consume the vector so you can access or modify it later. join()
creates a new, separate copy of the joined string, and any changes to the original vector will not affect the sentence
string.
Building a Character Frequency HashMap from a String
let mut freq_map = HashMap::new();
for c in s.chars() {
*freq_map.entry(c).or_insert(0) += 1;
}
Iterators
Iterators are lazy, they don't compute anything until they are consumed.
Ownership, reference and mutability
Passing references
Use &
(e.g., &v
for immutable reference, &mut v
for mutable reference). If you pass &v
, you are passing a borrowed immutable reference of v
. When accepting a reference in a function, declare the parameter as &Type
to signify that the function will accept an immutable reference.
fn my_function(v: &Vec<i32>)
fn modify_vector(v: &mut Vec<i32>)
&mut v
creates a mutable reference to the vector v
. You can modify v
directly through this reference. The original v
remains owned by the original scope, but you have mutable access to it.
Conclusion
Keep practicing to get better at Rust. If any syntax or code that you use a lot in your daily coding, please leave a comment! I'll add new sections based on your feedback!