Common RUST Patterns I wish Everyone knew

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

Contents

1. Using ? 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.

2. 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.

3. Using 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).

4. Getting owned value from a vector without cloning

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 .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 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.

5. Referring Values inside a struct

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 structs 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>:.