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

  • A+
Category:Languages

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) } 

 


As mentioned, Option and Result have tons of utility methods on them. Additionally, the try operator (?) can also be used for the extremely common case of "return the failure or unwrap the result"

I'd implement FromStr for Face and Suit. Your code would then look like:

impl FromStr for Card {     type Err = ();      fn from_str(s: &str) -> Result<Self, Self::Err> {         let face = s[0..1].parse()?;         let suit = s[1..2].parse()?;          Ok(Card { face, suit })     } } 

If you didn't / couldn't, you can use the existing methods on Option. You didn't define Foo::from_usize, so I assume to returns Foo, so it would use map:

fn from_str(s: &str) -> Result<Self, Self::Err> {     let mut c = s.chars();      let face = c         .next()         .and_then(|c| FACES.find(c))         .map(Face::from_usize)         .ok_or(())?;     let suit = c         .next()         .and_then(|c| SUITS.find(c))         .map(Suit::from_usize)         .ok_or(())?;      Ok(Card { face, suit }) } 

Both of these paths allow you to have useful errors, such as an enum that lets you know if the suit / face was missing / invalid. An error type of () is useless to consumers.

You could also define Suit::from_char and Face::from_char and not leak the implementation of the array out.

Putting it all together:

impl Suit {     fn from_char(c: char) -> Option<Self> {         use Suit::*;          [('c', C), ('d', D), ('h', H), ('s', S)]             .iter()             .cloned()             .find(|&(cc, _)| cc == c)             .map(|(_, s)| s)     } }  enum Error {     MissingFace,     MissingSuit,     InvalidFace,     InvalidSuit, }  impl FromStr for Card {     type Err = Error;      fn from_str(x: &str) -> Result<Self, Self::Err> {         use Error::*;          let mut xs = x.chars();          let face = xs.next().ok_or(MissingFace)?;         let face = Face::from_char(face).ok_or(InvalidFace)?;         let suit = xs.next().ok_or(MissingSuit)?;         let suit = Suit::from_char(suit).ok_or(InvalidSuit)?;          Ok(Card { face, suit })     } } 

fn join<T>(x: Option<Option<T>>) -> Option<T> 

This is x.and_then(|y| y)

fn bind<A, B, F>(x: Option<A>, f: F) -> Option<B> where     F: FnOnce(A) -> Option<B>, 

This is x.and_then(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>, 

This is x.and_then(f).and_then(g)

See also:

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: