在Rust中处理多个`Option< T>`的惯用方式是什么? [英] What's the idiomatic way to handle multiple `Option<T>` in Rust?

查看:455
本文介绍了在Rust中处理多个`Option< T>`的惯用方式是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

由于我对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.

我被多个Option<T>卡住了.太麻烦了,无法手动处理每个None案例.

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

例如,在Haskell中,可以将可选值(Maybe)操作与各种运算符链接在一起:fmap<*>>>=等:

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

在Rust中,这看起来是不可能的.我正在尝试将两个字符的卡字符串解析为结构Card:

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

这段代码在Haskell中看起来像这样:

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

由于通过>>=进行了链接,Haskell使得(并且很容易)操纵monad的内部值成为可能.为了实现与之接近的功能,我必须编写chain函数,这似乎很容易理解:

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

推荐答案

如上所述, Result 上有实用程序方法.此外,try运算符(?)也可以用于返回失败或取消包装结果"的极为常见的情况.

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"

我将为FaceSuit实现FromStr.您的代码将如下所示:

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

如果没有,则可以使用Option上的现有方法.您没有定义Foo::from_usize,所以我假设要返回Foo,因此它将使用map:

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

  • Option::and_then
  • Option::map
  • Option::ok_or
    • Option::and_then
    • Option::map
    • Option::ok_or
    • 这两个路径均允许您出现有用错误,例如一个枚举,可让您知道衣服/面孔是否丢失/无效.错误类型()对使用者没有用.

      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.

      您还可以定义Suit::from_charFace::from_char,而不会泄漏数组的实现.

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

      将它们放在一起:

      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>
      

      这是x.and_then(|y| y)

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

      这是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>,
      

      这是x.and_then(f).and_then(g)

      另请参阅:

      这篇关于在Rust中处理多个`Option&lt; T&gt;`的惯用方式是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆