Rust:如何在第一个 Err 或 None 上短路退出迭代器方法链?
Posted
技术标签:
【中文标题】Rust:如何在第一个 Err 或 None 上短路退出迭代器方法链?【英文标题】:Rust: How to short-circuit exit from a chain of iterator methods on first Err or None? 【发布时间】:2020-09-01 14:01:24 【问题描述】:考虑一个迭代器方法链:
.iter().a().b().c()
其中a
产生Option
(或Result
)类型的值。只要a
产生None
(或Err(_)
),有没有办法让整个链返回None
(或(Err(_)
)?
详细示例
给定函数valid
(识别无意义的输入)和accept
(一个
任意选择标准):
type T = u8;
type ERR = u8;
fn valid(x: &T) -> Result<T, ERR>
if *x < 10 Ok(*x) else Err(*x)
fn accept(x: &T) -> bool
if *x > 9 panic!(" should have been rejected by validator")
*x % 2 == 0
我想写一个函数
fn count_accepted(data: &[T]) -> Result<usize, ERR>
哪个
一旦遇到第一个无效元素就返回Err(ERR)
输入数据
如果所有元素都有效,则返回包含值计数的Ok(usize)
满足accept
标准
这是一个使用循环的解决方案:
fn count_loop(data: &[T]) -> Result<usize, ERR>
let mut count = 0;
for item in data
valid(&item)?;
if accept(&item) count += 1
Ok(count)
正如这些测试所证明的那样,这似乎可以按要求工作:
macro_rules! testem
($count:path) =>
#[test] fn empty() assert_eq!($count(&[]) , Ok(0))
#[test] fn all_ok_and_accepted() assert_eq!($count(&[2,6]) , Ok(2))
#[test] fn all_ok_some_rejected() assert_eq!($count(&[2,3]) , Ok(1))
#[test] fn one_invalid() assert_eq!($count(&[12]) , Err(12))
#[test] fn stop_on_first_invalid() assert_eq!($count(&[2,13,6,12,5]), Err(13))
mod test_loop testem!super::count_loop
我想了解是否/如何使用 迭代器而不是循环。
考虑一个相关但更简单的问题:如果任何数据无效,则保释
立即取出,否则将所有数据收集到一个向量中。换一种说法,
从上一个问题中删除accept
条件。
这个问题有一个比较满意的解决方案,因为FromIterator
Result
的实现负责提前终止:
fn related(data: &[T]) -> Result<Vec<T>, ERR>
data.iter()
.map(valid)
.collect()
mod test_related
#[test]
fn stop_on_first_invalid() assert_eq!(super::related(&[2,13,6,12,5]), Err(13))
这是related
的扩展,它通过了与count_loop
相同的测试:
fn count_via_vec(data: &[T]) -> Result<usize, ERR>
Ok(data
.iter()
.map(valid)
.filter(|x| x.is_err() || accept(&x.unwrap()))
.collect::<Result<Vec<T>, ERR>>()?
.len())
mod test_vvec testem!super::count_via_vec
但是:这个解决方案相对于count_loop
有很多缺点:
过滤条件非常嘈杂。
当第一个无效项有
已识别(与原始循环实现不同):出现?
比它应该晚 2 行...如果有意义的话。
向量被不必要地填充(除非 Rust 执行一些很酷的 我还没有意识到的优化),所以空间复杂度从 O(1) 到 O(N)。
最后一点通常可以通过替换来解决
.collect::<Result<Vec<T>, ERR>>()?.len())
和 .count()
,但这有
取消对无效案件的承认的进一步不利影响:他们
被简单地算作成功,正如测试失败所证明的那样
实现:
fn count_iterate(data: &[T]) -> Result<usize, ERR>
Ok(data
.iter()
.map(valid)
.filter(|x| x.is_err() || accept(&x.unwrap()))
.count())
mod test_iter testem!super::count_iterate
您能否建议一些在迭代器方法链中提前返回的机制 在这种情况下可以使用吗?
【问题讨论】:
您尝试过try_<function>
迭代器方法吗? try_for_each
似乎与您要查找的内容相似。 doc.rust-lang.org/std/iter/…
您最终找到任何干净的解决方案了吗?谢谢!
我发现了一些朝着正确方向迈出的一步,但是由于我已经远离 Rust 几个月了,记忆非常模糊。第一个是 docs.rs/fallible-iterator/0.2.0/fallible_iterator/index.html 。另一个是,在某些情况下,Error 的实现实际上可以很好地做到这一点(在某些博客文章中可能对此进行了讨论,也许是 Michael Snoyman 的?)这似乎是非常 i> 接近我想要的,但我目前无法找到它:-(
【参考方案1】:
您可能正在寻找具有impl<T, U, E> Sum<Result<U, E>> for Result<T, E> where T: Sum<U>
的std::iter::Sum
,而对于所有基本整数,Sum
也有 impls。
所以下面的就行了:
fn valid(x: &u32) -> Result<u32, u32>
if *x < 10 Ok(1) else Err(*x)
fn count(x: &[u32]) -> Result<u32, u32>
x.iter()
.map(valid)
.sum()
fn main()
println!(":?", count(&[13,1,2,3]));
正如Sum
上的文档所说,如果遇到错误,这将使迭代器短路。这将包括短路链式迭代器。
【讨论】:
Result
的 Sum
实现很有趣,谢谢。但是,修改valid
标准的需要需要使用适配器,到那时它就变得比循环方法丑得多。此外,该解决方案忽略了下游链(一般示例中为.b().c()
,具体示例中为filter(accept)
),我不知道如何解决。即使它可以修复,它也是对这一类别中一个问题的临时解决方案,而不是对问题顶部提出的问题的解决方案:让它为 `iter.a::以上是关于Rust:如何在第一个 Err 或 None 上短路退出迭代器方法链?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 rust 中迭代 vec 或 slice 的前缀或后缀?
Twitter 流 API 从 None 给出 JSONDecodeError("Expecting value", s, err.value)