Rust如何将Result<Vec<T>,E>迭代器扁平化为Result<T,E>迭代器
编译错误原因
match要求所有分支返回值类型完全一致,你的代码里两个分支返回的是不同的迭代器类型:
- Ok分支返回类型为
Map<slice::Iter<'_, u32>, fn(&u32) -> Result<&u32, ...>>的迭代器 - Err分支返回类型为
Once<Result<..., &&str>>的迭代器
两种类型没有隐式统一转换规则,因此编译器报类型不匹配错误。
修复方案
方案1:最小改动,用Either统一迭代器类型
使用either crate提供的Either枚举(该枚举本身已实现Iterator特性)包装两个分支的迭代器,只要两个迭代器产出的Item类型一致即可正常编译。
首先在Cargo.toml添加依赖:
[dependencies] either = "1.13"
修改代码如下:
use either::Either; use std::iter::once; fn main() { let v1: Vec<Result<Vec<u32>, &str>> = vec![ Ok(vec![1,2,3]), Ok(vec![4,5,6,9]), Err("failed"), ]; let iter1 = v1.iter(); let flattened_iter1 = iter1.map(|x| match x { Ok(v) => Either::Left(v.iter().map(Ok)), Err(e) => Either::Right(once(Err(e))), }).flatten(); for r in flattened_iter1 { match r { Ok(i) => println!("{}", i), Err(_) => { println!("Error"); break }, } } }
方案2:无第三方依赖,动态分发统一类型
如果不想引入外部crate,可以将两个分支的迭代器装箱为动态分发的特征对象,统一类型:
use std::iter::once; fn main() { let v1: Vec<Result<Vec<u32>, &str>> = vec![ Ok(vec![1,2,3]), Ok(vec![4,5,6,9]), Err("failed"), ]; let iter1 = v1.iter(); let flattened_iter1 = iter1.map(|x| -> Box<dyn Iterator<Item = _>> { match x { Ok(v) => Box::new(v.iter().map(Ok)), Err(e) => Box::new(once(Err(e))), } }).flatten(); for r in flattened_iter1 { match r { Ok(i) => println!("{}", i), Err(_) => { println!("Error"); break }, } } }
注意:动态分发会带来可忽略的极小运行时开销,绝大多数业务场景下无感知。
更优原生实现(无额外依赖、无运行时开销)
不需要手动处理迭代器类型包装,直接用标准库的flat_map结合Result的方法即可实现相同逻辑,代码更简洁,且所有类型在编译期可确定,无额外开销:
fn main() { let v1: Vec<Result<Vec<u32>, &str>> = vec![ Ok(vec![1,2,3]), Ok(vec![4,5,6,9]), Err("failed"), ]; let flattened_iter1 = v1.iter() .flat_map(|res| { res.as_ref() .map(|v| v.iter().map(Ok)) .unwrap_or_else(|e| std::iter::once(Err(e))) }); for r in flattened_iter1 { match r { Ok(i) => println!("{}", i), Err(_) => { println!("Error"); break }, } } }
如果你需要迭代得到拥有所有权的
u32值而非&u32引用,只需要把v1.iter()替换为v1.into_iter(),同时把v.iter().map(Ok)替换为v.into_iter().map(Ok)即可,逻辑完全一致。
内容的提问来源于stack exchange,提问作者AlexKing
相关产品推荐
相关产品推荐

