Note: This post may expand in the future. I plan to add new patterns. If you have suggestions for nice to have patterns, send your suggestions to the mail address: akurmustafa@gmail.com
? operator for early exit.Vec<Result<T>> to Result<Vec<T>> conversionizip! for iterating multiple collections simultaneously.? Operator for early exit Assume you have the following function with signature
fn process<T>(value: T) -> Result<T> {...}
And you use the function above in another function as below
fn main() -> Result<()> {
let value: T = T::new();
match process(value) => {
Ok(result) => {
// use result in subsequent stages
...
},
Err(e) => return Err(e),
}
}
where the error case is propagated out of the main function also. Matching the Ok case, and propagating the Err case out of scope by early exiting is so common that there is an operator for this case. The above function can be rewritten as
fn main() -> Result<()> {
let value: T = T::new();
let result = process(value?;
// use result in subsequent stages
...
}
using ? operator.
A similar pattern can be used to early exit from Option None.
Vec<Result<T>> to Result<Vec<T>> Conversion Assume you have the following Rust function
fn process<T>(val: T) -> Result<T> {
...
}
and you want to process each entry in the Vec<T> with the function above. To do this you would call the following iterator
let entries: Vec<T> = vec![...];
let processed_entries = entries.into_iter().map(|entry| process(entry)).collect::<Vec<_>>();
For the above code snippet the compiler would generate type: Vec<Result<T>> for processed_entries. However, for most use cases, we do not care which entries were processed successfully, and which were not. What we care about is whether there is an error during processing or not. Hence if there is any error, we return error. To accomplish this we can do the following iteration
fn main() -> Result<()> {
...
let processed_entries_without_error = vec![];
for entry in processed_entries {
processed_entries_without_error.push(entry?);
}
...
}
After iteration above, entries will have the Vec<T> type, if any of the entries in the processed_entries have an error, main function would return error. For this common pattern we can just use the following code snippet
fn main() -> Result<()> {
...
let entries: Vec<T> = vec![...];
let processed_entries_without_error = entries.into_iter().map(|entry| process(entry)).collect::<Result<Vec<_>>>()?;
...
}
where, processed_entries_without_error has type Vec<T>, and main function returns error, if there is an error during processing of any entry inside entries.
A similar conversion applies to Vec<Option<T>> to Option<Vec<T>> conversion.
izip! to iterate multiple collections simultaneously Assume you want to iterate vec1: Vec<T> and vec2: Vec<T> simultaneously. To do so, you can use the .zip iterator below:
let vec1 = vec![1,2,3,4];
let vec2 = vec![1,2,3,4];
for (elem1, elem2) in vec1.into_iter().zip(vec2){
// use elem1, elem2
}
However, when there are more than 2 collections to iterate over, you cannot use .zip directly, since it only iterates two iterators. By nesting iterations, you can iterate simultaneously using more than 2 collections using .zip as below:
let vec1 = vec![1,2,3,4];
let vec2 = vec![1,2,3,4];
let vec3 = vec![1,2,3,4];
for (elem1, (elem2, elem3)) in vec1.into_iter().zip(vec2.into_iter().zip(vec3)){
// use elem1, elem2, elem3
}
However, it is ugly. An alternative would be
let vec1 = vec![1,2,3,4];
let vec2 = vec![1,2,3,4];
let vec3= vec![1,2,3,4];
for idx in 0..vec1.len(){
let elem1 = vec1[idx];
let elem2 = vec2[idx];
let elem3 = vec3[idx];
// use elem1, elem2, elem3
}
The alternative is not good either. However, with izip! macro we can iterate more than 2 collections simultaneously, without sacrificing readability. With izip!, the code snippets above can be rewritten as:
let vec1 = vec![1,2,3,4];
let vec2 = vec![1,2,3,4];
let vec3= vec![1,2,3,4];
for (elem1, elem2, elem3) in izip!(vec1, vec2, vec3){
// use elem1, elem2, elem3
}
This version is much more readable, and concise.
However, please note that izip! macro resides in the itertools crate, which is not in the standard library (It is very common though).
Assume you have an owned vector that you want to use, and you only need 0th and 1st indices of this vector.
What you would normally do is the following:
let entries: Vec<String> = vec![String::new(); 4];
let first_entry: String = entries[0].clone();
let second_entry: String = entries[1].clone();
However, if we won’t use entries anymore, code snippet above uses redundant .clones to take ownership. Instead we can use .swap_remove method of the vector. The version with .swap_remove is as follows:
let mut entries: Vec<String> = vec![String::new(); 4];
let second_entry: String = entries.swap_remove(1);
let first_entry: String = entries.swap_remove(0);
There are a couple of differences between the code snippets above. The first one is that the entries vector is now mutable. And we changed the removal order instead of getting 0th and 1st indices we got 1st and 0th indices respectively. These changes are related to how .swap_remove is implemented. .swap_remove(idx) method swaps the element at its argument idx with the last entry in the vector (which is an O(1) operation). Then it removes the last entry (which was the entry at the argument idx), and returns it (It is not necessarily implemented in this way, however it behaves as if implemented this way). In short, code snippet above is functionally equivalent to code below:
let mut entries: Vec<String> = vec![String::new(); 4];
let last_idx = entries.len() - 1;
entries.swap(1, last_idx);
let second_entry = entries.pop().unwrap();
let last_idx = entries.len() - 1;
entries.swap(0, last_idx);
let first_entry = entries.pop().unwrap();
As an example, consider the following vector: entries = [a,b,c,d,e], after .swap_remove(1), entries vector becomes [a,e,c,d], and method call returns b.
For this reason, entries vector now needs to be mutable. Secondly, we need to remove entries in descending order to make sure that .swap_remove indeed returns the correct entry in the original vector.
Assume you have the following struct
pub struct Tuple3 {
first: String,
third: String,
};
and you have an instance of struct above as below:
let tuple3 = Tuple3 {
first: "first".to_string(),
second: "second".to_string(),
third: "third".to_string(),
};
when you want to refer to fields of this struct, you have to use the following syntax tuple3.first, tuple3.second, etc.
However, this is a bit verbose, we want to refer to fields with less typing. One alternative solution might be reassigning fields to new variables for easy reference. Such as below:
let first: String = tuple3.first;
let second: String = tuple3.second;
let third: String = tuple3.third;
// We can use variables `first`, `second`, `third` from now on
However, this is a bit repetitive. With RUST pattern matching, we can expand fields inside structs to new variables. The syntax is as below:
let Tuple3 {
first,
second,
third,
} = tuple3;
// We can use variables `first`, `second`, `third` from now on
We might need to refer to only 2 members, and not care about others. In this case, we don't want to list all members as above (imagine you have 10 members). We can use the following syntax to get only a subset of fields
let Tuple3 {
first,
third,
..
} = tuple3;
// We can use variables `first`, `third` from now on
where fields other than, first and third are ignored. By the way, we don't need to keep original field order during struct pattern matching as long as field names match. So the following code snippet is functionally equivalent to the above
let Tuple3 {
third,
first,
..
} = tuple3;
// We can use variables `first`, `third` from now on
where field third is written before first.
One downside of this pattern is that, we need to use the original field names. However, in our application we might need to change the name of the variable to make intent clear and code more readable.
Imagine, we use field first for width, field second for height and field third for depth.
Without pattern matching, we can just use the following code snippet for renaming original field names.
let width: String = tuple3.first;
let height: String = tuple3.second;
let depth: String = tuple3.third;
// We can use variables `width`, `height`, `depth` from now on
Similarly, we can accomplish the same thing with struct pattern matching as below:
let Tuple3 {
first: width,
second: height,
third: depth,
} = tuple3;
// We can use variables `width`, `height`, `depth` from now on
where desired names are written after <field_name>:.