如何返回包含迭代器中每个Result的错误而不仅仅是第一个Result的错误?

6

我正在尝试在Rust中实现一个简单的解释器,为此我创建了一个Tokens结构体,它接收源代码字符并产生一个TokenScanError,存储在Result中:

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 向量中:

fn scan_tokens(source: &str) -> Result<Vec<Token>, ScanError> {
    let iter = Tokens {
        chars: source.chars(),
    };

    iter.collect()
}

在出现多个错误的情况下,我真的希望返回每一个错误:

fn scan_tokens(source: &str) -> Result<Vec<Token>, Vec<ScanError>> {
    // what goes here?
}

据我所知,由于 FromIterator 特征或 Result 不在我的crate中,因此我不可能实现自己的版本。 有人能提供一种干净的方法吗?
我已经编写了一个使用迭代器上的 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())
}

1
有没有更好的函数式方法来处理带有错误检查的向量? - Stargateur
同意这基本上是那个问题的重复;当我搜索时,我只发现了停止在第一个错误场景。 - avy
@avy 就此问题而言,它的措辞和焦点要比那个问题好得多;我还不知道它们是否是很好的重复。 - Shepmaster
2个回答

9

解包每个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)
    }
}

另请参阅:

您还可以使用 OptionResult 实现的 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())
    }
}

另请参阅:


5

采用命令式解决方案通常是实现某些算法最具表现力和高效的方式。这是Rust,不是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)
    }
}

3
“一种命令式的解决方案通常是实现某些算法最表达和高效的方式。” 我不同意这个观点。“这是 Rust,不是 Haskell;并不是所有的东西都需要是函数式的。” 我同意这个观点。 - Stargateur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接