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 following function with signature
fn process<T>(value: T) -> Result<T> {...}
And you use 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 error case is propagated out of main
function also. Matching Ok
case, and propagating Err
case to out of scope by early exiting is so common that there is an operator for this case. 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.
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 function above. To do this you would call following iterator
let entries: Vec<T> = vec![...];
let processed_entries = entries.into_iter().map(|entry| process(entry)).collect::<Vec<_>>();
For above code snippet compile would generate type: Vec<Result<T>>
for processed_entries
. However, for most use cases, we do not care which entries processed successfully, and which do not. What we care is that, whether there is an error during processing or not. Hence if there is any error, we return error. To accomplish this we can do 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 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 the any entry inside entries
.
Similar Conversion applies 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 .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 is 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!
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 reside in itertools
crate, which is not in 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 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 .clone
s 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 difference between code snippets above. First one is 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 element at its argument idx
with the last entry in the vector (which is an O(1) operation). Then removes the last entry (which was the entry at the argumentidx
), and returns it (It is not necessarily implement 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 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 need to be mutable. Secondly, we need to remove entries in descending order to make sure that .swap_remove
indeed returns correct entry in the original vector.
Assume you have following struct
pub struct Tuple3 {
first: String,
third: String,
};
and you have an istance 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 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 variable 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 struct
s to new variable. 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 only 2 members, doesnt't care about others. In this case, we don't want to list all members as above (imagine you have 10 members). We can use following syntax to get only 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 following code snippet is functionally equivalent to 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 original field name. However, in our application we might need to change 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 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
Similary, we can accomplish same thing with stuct patten 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>:
.