在Rust中处理多个`Option< T>`的惯用方式是什么? [英] What's the idiomatic way to handle multiple `Option<T>` in Rust?
问题描述
由于我对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"
我将为Face
和Suit
实现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_char
和Face::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< T>`的惯用方式是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!