ferrous-systems / rust-training

Learning materials for the Rust Training courses by Ferrous Systems
127 stars 16 forks source link

Idea: how to eliminate / avoid `Option::unwrap` calls #44

Open justahero opened 1 year ago

justahero commented 1 year ago

For people making use of unwrap calls in the code may want to find ways how to eliminate them. Not all unwrap calls are necessary cases for errors, in particular for Option::unwrap.

The following is a short list of examples that avoid the use of unwrap while not panicking. Instead of handling both Some & None via a match statement std Rust offers some alternatives to achieve the same, in particular for iterators.


struct Item(pub Option<&'static str>);

fn main() {
    let input = vec![Item(None), Item(Some("Hello")), Item(Some("World"))];
    let output = input
        .iter()
        .filter(|i| i.0.unwrap().to_lowercase() == "world")
        .collect::<Vec<_>>();
    println!("Found {} items", output.len());
}

This will panic, but what we would like to return here is the number of items that match the (lowercased) world string. The filter line could be written as:

filter(|i| i.0.map_or(false, |i| i.to_lowercase() == "world"))

Using the rowan library assume we would like to return a list of two tokens from the parent node.

let syntax_tokens = t
    .parent()
    .unwrap()
    .siblings_with_tokens(Direction::Next)
    .take(2)
    .collect::<Vec<_>>();

This fails when the given token t does not have a parent. To ignore this case we can rewrite it using the Option::into_iter function to transform the Option into an Iterator. If the Option holds a Some value the iterator executes exactly once, otherwise zero for None .

let syntax_tokens = t
    .parent()
    .into_iter()
    .flat_map(|it| it.siblings_with_tokens(Direction::Next))
    .take(2)
    .collect::<Vec<_>>();

A call to flat_map is necessary here, otherwise it returns an Iterator in a Vec (list in list).


Use filter_map to allow use of ? operator.

struct Number(pub i32);
fn main() {
    let items = [None, Some(Number(1)), Some(Number(2))];
    let sum: i32 = items.into_iter().map(|x| x.unwrap().0).sum();
    println!("Sum is {sum}");
}

rewritten as:

struct Number(pub i32);

fn main() {
    let items = [None, Some(Number(1)), Some(Number(2))];
    let sum: i32 = items.into_iter().filter_map(|x| Some(x?.0)).sum();
    println!("Sum is {sum}");
}

Closures that return an Option or Result allow the ? operator. Method filter_map uses an Option as return type.

vacuus commented 10 months ago

Your first example could use .count() and avoid the Vec altogether.