在 Rust 中处理多个 `Option<T>` 的惯用方法是什么?
What's the idiomatic way to handle multiple `Option<T>` in Rust?
由于我是 Rust 的新手,我需要有关如何以惯用方式完成错误处理的指导。我发现错误处理样板文件真的很烦人。
我遇到了多个 Option<T>
。手动处理每个 None
个案例太冗长了。
例如,在 Haskell 中,您可以使用各种运算符链接可选值 (Maybe
) 操作:fmap
、<*>
、>>=
, 等等:
f x = x * x
g x = x ++ x
main = print $ g <$> show <$> f <$> Just 2
这在 Rust 中看起来是不可能的。我正在尝试将两个字符的卡片字符串解析为结构 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 中看起来像这样:
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
函数,这看起来非常不合常理:
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)
}
看来你想要Option::and_then
:
pub fn and_then<U, F>(self, f: F) -> Option<U>
where
F: FnOnce(T) -> Option<U>
示例:
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);
Maybe
-Rust 的 Result
中的单子链是由 the try!
macro 完成的。应该看起来像
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))
}
如前所述,Option
and Result
有 吨 实用方法。此外,try 运算符 (?
) 也可用于 "return the failure or unwrap the result"
这种极其常见的情况
我会为 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 })
}
}
如果您没有/不能,您可以使用 Option
上的现有方法。你没有定义 Foo::from_usize
,所以我假设 returns Foo
,所以它会使用 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 })
}
这两个路径都允许您有有用的错误,例如让您知道西装/面部是否丢失/无效的枚举。 ()
的错误类型对消费者无用。
您还可以定义 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 })
}
}
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)
另请参阅:
除了其他答案之外,您还可以查看像 mdo or map_for 这样的单子表达式箱。例如 map_for
:
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(()))
}
完全披露:我是 map_for
箱子的作者。
由于我是 Rust 的新手,我需要有关如何以惯用方式完成错误处理的指导。我发现错误处理样板文件真的很烦人。
我遇到了多个 Option<T>
。手动处理每个 None
个案例太冗长了。
例如,在 Haskell 中,您可以使用各种运算符链接可选值 (Maybe
) 操作:fmap
、<*>
、>>=
, 等等:
f x = x * x
g x = x ++ x
main = print $ g <$> show <$> f <$> Just 2
这在 Rust 中看起来是不可能的。我正在尝试将两个字符的卡片字符串解析为结构 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 中看起来像这样:
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
函数,这看起来非常不合常理:
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)
}
看来你想要Option::and_then
:
pub fn and_then<U, F>(self, f: F) -> Option<U>
where
F: FnOnce(T) -> Option<U>
示例:
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);
Maybe
-Rust 的 Result
中的单子链是由 the try!
macro 完成的。应该看起来像
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))
}
如前所述,Option
and Result
有 吨 实用方法。此外,try 运算符 (?
) 也可用于 "return the failure or unwrap the result"
我会为 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 })
}
}
如果您没有/不能,您可以使用 Option
上的现有方法。你没有定义 Foo::from_usize
,所以我假设 returns Foo
,所以它会使用 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 })
}
这两个路径都允许您有有用的错误,例如让您知道西装/面部是否丢失/无效的枚举。 ()
的错误类型对消费者无用。
您还可以定义 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 })
}
}
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)
另请参阅:
除了其他答案之外,您还可以查看像 mdo or map_for 这样的单子表达式箱。例如 map_for
:
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(()))
}
完全披露:我是 map_for
箱子的作者。