由于我对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) }推荐答案
如上所述, Option 和 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
- 如何在Option上实现一些便捷的方法(例如flat_map,flatten)?
这两个路径均允许您出现有用错误,例如一个枚举,可让您知道衣服/面孔是否丢失/无效.错误类型()对使用者没有用.
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)
另请参阅: