Rust 什么';s处理多个选项的惯用方法<;T>;`锈迹斑斑?

Rust 什么';s处理多个选项的惯用方法<;T>;`锈迹斑斑?,rust,monads,option,maybe,Rust,Monads,Option,Maybe,因为我对Rust还比较陌生,所以我需要关于如何按照惯用方式进行错误处理的指导。我发现错误处理样板文件真的很烦人 我被多个选项困住了。它太冗长,无法手动处理每个None案例 例如,在Haskell中,您可以将可选值(可能)操作与各种运算符链接起来:fmap,,=,等等: fx=x*x gx=x++x main=print$g仅显示f 2 同样的情况在锈迹中看起来是不可能的。我正在尝试将一个两个字符的卡片字符串解析为一个structcard: const FACES: &'static s

因为我对Rust还比较陌生,所以我需要关于如何按照惯用方式进行错误处理的指导。我发现错误处理样板文件真的很烦人

我被多个
选项困住了。它太冗长,无法手动处理每个
None
案例

例如,在Haskell中,您可以将可选值(
可能
)操作与各种运算符链接起来:
fmap
=
,等等:

fx=x*x
gx=x++x
main=print$g仅显示f 2
同样的情况在锈迹中看起来是不可能的。我正在尝试将一个两个字符的卡片字符串解析为一个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的链接,使得操纵单子的内部值成为可能(而且很容易!)。为了实现类似的功能,我必须编写
chain
函数,这似乎非常不符合逻辑:

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)
}
fn连接(x:Option)->Option{
如果让一些(y)=x{
Y
}否则{
没有一个
}
}
fn绑定(x:Option,f:f)->Option
哪里
F:fNoce(A)->选项,
{
加入(x.map(f))
}
fn链(x:Option,f:f,g:g)->Option
哪里
F:fNoce(A)->选项,
G:FnOnce(B)->选项,
{
绑定(绑定(x,f),g)
}
您似乎想要:

pub fn和_then(self,f:f)->选项
哪里
F:FnOnce(T)->选项
示例:

fnsq(x:u32)->选项{Some(x*x)}
fn nope(u32)->选项{None}
断言!(一些(2)和(sq)和(sq),一些(16));
断言!(一些(2)和(sq)和(不),没有);
断言!(一些(2)。然后(不)。然后(sq),没有);
断言!(无。然后(sq)。然后(sq),无);

可能
-Rust的
结果中的一元链结
是通过以下方式完成的。应该看起来像

fn from_str(x: &str) -> Result<Self, Self::Err> {
    let mut xs = x.chars();
    let a = try!(chain(xs.next(), |x| FACES.find(x), Face::from_usize));
    let b = try!(chain(xs.next(), |x| SUITS.find(x), Suit::from_usize));
    Ok(Card::new(face, suit))
}
fn from_str(x:&str)->结果{
设mut xs=x.chars();
让a=try!(chain(xs.next(),|x | FACES.find(x),Face::from_usize));
让b=try!(chain(xs.next(),|x | SUITS.find(x),Suit::from_usize));
Ok(卡片:新的(脸、套装))
}
如前所述,上面有大量实用方法。此外,try操作符(
)也可用于极为常见的“返回故障或打开结果”的情况

我会为
Face
Suit
实现
FromStr
。您的代码将如下所示:

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 })
    }
}
这两种路径都允许您出现有用的错误,例如一个枚举,可以让您知道西装/面是否丢失/无效。
()
的错误类型对使用者没有用处

您还可以定义
Suit::from_char
Face::from_char
而不泄漏数组的实现

总而言之:

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 })
    }
}
这是
x,然后是(f)

fn链

除了其他答案,您还可以查看一元表达式板条箱,如或。例如,对于
,使用
map\u:

fn from_str(x: &str) -> Result<Self, Self::Err> {
    let mut xs = x.chars();
    map_for!{
        ax <- xs.next();
        f  <- FACES.find(ax);
        a  <- Face::from_usize(f);
        bx <- xs.next();
        s  <- SUITS.find(bx);
        b  <- Suit::from_usize (s);
        => Card::new(a, b) }
    .ok_or(Err(()))
}
fn from_str(x:&str)->结果{
设mut xs=x.chars();
地图{

既然
已经稳定下来,我想
试试看!
就要退出了。
让a=chain(xs.next(),|x | FACES.find(x),Face::from_usize)?;
看起来好一点。有意思。这基本上是旧版的语法糖吗?
试试看!
?@leftaround实际上它更好,因为它是由驱动的,所以它更灵活。例如,它适用于
选项
,当它稳定后,它将可用于用户类型。
fn join<T>(x: Option<Option<T>>) -> Option<T>
fn bind<A, B, F>(x: Option<A>, f: F) -> Option<B>
where
    F: FnOnce(A) -> Option<B>,
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>,
fn from_str(x: &str) -> Result<Self, Self::Err> {
    let mut xs = x.chars();
    map_for!{
        ax <- xs.next();
        f  <- FACES.find(ax);
        a  <- Face::from_usize(f);
        bx <- xs.next();
        s  <- SUITS.find(bx);
        b  <- Suit::from_usize (s);
        => Card::new(a, b) }
    .ok_or(Err(()))
}