我正在尝试在Rust中实现一个简单的解释器,为此我创建了一个Tokens
struct,该结构构成源字符并在Result
中产生Token
或ScanError
:
pub struct Tokens<'src> {
chars: Chars<'src>,
}
impl<'src> Iterator for Tokens<'src> {
type Item = Result<Token, ScanError>;
fn next(&mut self) -> Option<Result<Token, ScanError>> {
// ...
}
}
由于Result
实现了FromIterator
,因此将结果收集到第一个ScanError
或Token
S的向量很容易
fn scan_tokens(source: &str) -> Result<Vec<Token>, ScanError> {
let iter = Tokens {
chars: source.chars(),
};
iter.collect()
}
在多个错误的情况下,我真的想返回 ever 错误:
fn scan_tokens(source: &str) -> Result<Vec<Token>, Vec<ScanError>> {
// what goes here?
}
据我所知,无法实现我自己的FromIterator
版本,因为该性状或Result
都不是我的板条箱本地的。谁能提出一种干净的方法?
我在迭代器上使用partition
编写了实施,然后在下面解开每个Result
,但阅读并不像迭代器那样有趣:
type T = Vec<Result<Token, ScanError>>;
fn scan_tokens(source: &str) -> Result<Vec<Token>, Vec<ScanError>> {
let iter = Tokens {
chars: source.chars(),
};
let (tokens_results, error_results): (T, T) = iter.partition(|result| result.is_ok());
let errors: Vec<ScanError> = error_results
.into_iter()
.map(|result| result.unwrap_err())
.collect();
if errors.len() > 0 {
return Err(errors);
}
Ok(tokens_results
.into_iter()
.map(|result| result.unwrap())
.collect())
}
解开每个
Result
我会使用itertools的partition_map
来避免拆开的需要:
use itertools::{Either, Itertools}; // 0.8.0
fn iterator() -> impl Iterator<Item = Result<i32, bool>> {
vec![Ok(1), Err(false), Ok(2), Err(true), Ok(3)].into_iter()
}
fn example() -> Result<Vec<i32>, Vec<bool>> {
let (values, errors): (Vec<_>, Vec<_>) = iterator().partition_map(|v| match v {
Ok(v) => Either::Left(v),
Err(e) => Either::Right(e),
});
if errors.is_empty() {
Ok(values)
} else {
Err(errors)
}
}
另请参见:
- 与结果的迭代器一起工作的最惯用方法是什么?
- 当迭代器::映射返回结果:: err? 时,我该如何停止迭代并返回错误
- 如何在不收集临时向量的情况下对结果进行迭代计算?
您也可以使用Option
和Result
实现IntoIterator
的事实来避免使用确切的unwrap
,尽管这仍然是两次处理:
fn example2() -> Result<Vec<i32>, Vec<bool>> {
let (values, errors): (Vec<_>, Vec<_>) = iterator().partition(|result| result.is_ok());
if errors.is_empty() {
Ok(values.into_iter().flat_map(Result::ok).collect())
} else {
Err(errors.into_iter().flat_map(Result::err).collect())
}
}
另请参见:
- 为什么``选项''支持`interator`?
必应解决方案通常是实现某种算法的最具表现力和有效的方法。它是生锈的,不是haskell;并非一切都需要实用。
fn scan_tokens(source: &str) -> Result<Vec<Token>, Vec<ScanError>> {
let iter = Tokens {
chars: source.chars(),
};
let mut tokens = Vec::new();
let mut errors = Vec::new();
for result in iter {
match result {
Ok(token) => {
tokens.push(token);
}
Err(e) => {
errors.push(e);
}
}
}
if errors.is_empty() {
Ok(tokens)
} else {
Err(errors)
}
}