Rust Idioms

Resources

Below are some great resource that were used for this page:

Pattern Matching

enum Foo {
    A(i32),
    B,
    C
}

let foo = match x {
    Foo::A(n) => n,
    _ => 0,
};

Option Type

enum Option<T> {
    Some(T),
    None
}

Result Type

enum Result<T, E> {
    Ok(T),
    Err(E)
}

Example:

fn h(value: i32) -> Result<i32, String> {
    match i {
        i if i >= 0 => Ok(i + 10),
        _ => Err(format!("Input to h less than 0, found {}", i))
    }
}

fn main() {
    let input: i32 = 4;
    match h(input) {
        Ok(value) => println!("Result: {}", value),
        Err(e) => println!("Error: {}", e)
    }
}

if let

if let Ok(i) = h() {
    // do something
}

We ignore any error.

?

Common idiom:

let i = match h() {
    Ok(i) => i,
    err => return err  // throw the error
};

Equals to the following one liner:

let i = h()?;

map (some method)

fn add_four(x: i32) -> i32 {
    x + 4
}

fn maybe_add_four(y: Option<i32>) -> Option<i32> {
    match y {
        Some(yy) => Some(add_four(yy)),
        None => None
    }
}

Use of map:

fn add_four(x: i32) -> i32 {
    x + 4
}

fn maybe_add_four(y: Option<i32>) -> Option<i32> {
    y.map(add_four)
}

Clojure form:

fn maybe_add_four(y: Option<i32>) -> Option<i32> {
    y.map(|x| x + 4)
}

and_then, filter

“Idiomatization” (🤔…) of the following:

fn foo(input: Option<i32>) -> Option<i32> {
    if input.is_none() {
        return None;
    }
    let input = input.unwrap();
    if input < 0 {
        return None;
    }
    Some(input)
}

Use of “?”:

fn foo(input: Option<i32>) -> Option<i32> {
    let input = input?;  // return None if None
    if input < 0 {
        return None;
    }
    Some(input)
}

Use of and_then:

fn foo(input: Option<i32>) -> Option<i32> {
    input.and_then(|i| {
        if i < 0 {
            None
        } else {
            Some(i)
        }
    }
}

Use of filter:

fn foo(input: Option<i32>) -> Option<i32> {
    input.filter(|i| i >= 0);
}

ok_or

“Idiomatization” (🤔…) of the following:

fn bar(input: Option<i32>) -> Result<i32, ErrNegative> {
    match foo(input) {
        Some(n) => Ok(n),
        None => ErrNegative
    }
}
fn bar(input: Option<i32>) -> Result<i32, ErrNegative> {
    foo(input).ok_or(ErrNegative)
}

Iterators

fn ping_all(foos: &[Foo]) {
    for f in foos {
        f.ping();
    }
}

Becomes

fn ping_all(foos: &[Foo]) {
    foos.iter().for_each(|f| f.ping());
}

map, filter, for_each methods

let vec = vec![0, 1, 2, 3];
vec.iter()
   .map(|x| x + 1)
   .filter(|x| x > 1)
   .for_each(|x| println!("{}", x));

chain, enumerate methods

let vec = vec![0, 1, 2, 3];
for (i, v) in vec.iter()
                  .chain(vec![4, 5, 6, 7].iter())
                  .enumerate() {
    // do something
}

chain adds an iterator to an iterator.

collect method

let vec = vec![0, 1, 2, 3];
let vec_2: Vec<_> = vec.iter().map(|x| x + 2).collect();
let map: HashMap<_, _> = vec.iter()
                            .map(|x| x * 2)
                            .enumerate()
                            .collect();

_ is used to let the compiler infer the type for us. Type declarations (Vec, HashMap) are explicitely needed though to help collect to figure out what needs to be returned.