Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the idiomatic way to handle multiple `Option<T>` in Rust?

Since I'm fairly new to Rust, I need guidance on how error handling is done idiomatically. I find the error-handling boilerplate really annoying.

I'm stuck with multiple Option<T>s. It's too verbose to handle each None case manually.

In Haskell, for example, you can chain optional value (Maybe) operations with a variety of operators: fmap, <*>, >>=, etc.:

f x = x * x
g x = x ++ x
main = print $ g <$> show <$> f <$> Just 2

The same looks impossible in Rust. I'm trying to parse a two-character card string into a struct Card:

const FACES: &'static str = "23456789TJQKA";
const SUITS: &'static str = "CDHS";
enum Face { /* ... */ }
enum Suit { C, D, H, S }
struct Card {
    face: Face,
    suit: Suit
}
impl FromStr for Card {
    type Err = ();
    fn from_str(x: &str) -> Result<Self, Self::Err> {
        let mut xs = x.chars();
        let a = chain(xs.next(), |x| FACES.find(x), Face::from_usize);
        let b = chain(xs.next(), |x| SUITS.find(x), Suit::from_usize);
        if let (Some(face), Some(suit)) = (a, b) {
            Ok(Card::new(face, suit))
        } else {
            Err(())
        }
    }
}

This code would look like this in Haskell:

import Data.List (elemIndex)
x = Just 'C'
suits = "CDHS"
data Suit = C | D | H | S deriving Show
fromInt 0 = C
find = flip elemIndex
main = print $ x >>= find suits >>= return . fromInt

Thanks to the chaining via >>= Haskell makes it possible (and easy!) to manipulate the inner value of a monad. In order to achieve something close to that I had to write the chain function, which seems strongly unidiomatic:

fn join<T>(x: Option<Option<T>>) -> Option<T> {
    if let Some(y) = x {
        y
    } else {
        None
    }
}

fn bind<A, B, F>(x: Option<A>, f: F) -> Option<B>
where
    F: FnOnce(A) -> Option<B>,
{
    join(x.map(f))
}

fn chain<A, B, C, F, G>(x: Option<A>, f: F, g: G) -> Option<C>
where
    F: FnOnce(A) -> Option<B>,
    G: FnOnce(B) -> Option<C>,
{
    bind(bind(x, f), g)
}
like image 370
qwe Avatar asked Jun 07 '18 00:06

qwe


1 Answers

It seems like you want Option::and_then:

pub fn and_then<U, F>(self, f: F) -> Option<U> 
where
    F: FnOnce(T) -> Option<U>

Examples:

fn sq(x: u32) -> Option<u32> { Some(x * x) }
fn nope(_: u32) -> Option<u32> { None }

assert_eq!(Some(2).and_then(sq).and_then(sq), Some(16));
assert_eq!(Some(2).and_then(sq).and_then(nope), None);
assert_eq!(Some(2).and_then(nope).and_then(sq), None);
assert_eq!(None.and_then(sq).and_then(sq), None);
like image 171
Jorge Israel Peña Avatar answered Sep 28 '22 02:09

Jorge Israel Peña