如何在Rust中对带副作用的迭代器执行分区操作?
问题
我希望对带有副作用的迭代器进行分区操作,示例需求如下:
let arr = vec![1, 2, 3, 4, 5, 6]; let (even, odd) = arr.iter_mut().partition( |num: &&mut usize| if num % 2 == 0 { num *= 2; true } else { false } ); // 期望 even = [ 4, 8, 12 ] // 期望 odd = [ 1, 3, 5 ]
这段代码无法正常工作,因为partition闭包中的num类型是&&mut usize。我想知道有没有办法在Rust中无需分步操作(比如先移除num *= 2,再在分区后使用map)就能实现该需求。
我不想分步处理的原因是副作用可能会带来较高开销。目前我在实现自定义#[derive(Builder)]时采用了分步操作,代码如下:
let (required_fields, optional_fields): (Vec<_>, Vec<_>) = fields.partition(|f| extract_type_from_option(&f.ty).is_none()); let (required_fields_ident, required_fields_ty): (Vec<_>, Vec<_>) = required_fields .iter() .map(|f| (f.ident.clone().unwrap(), f.ty.clone())) .unzip(); let (optional_fields_ident, optional_fields_ty): (Vec<_>, Vec<_>) = optional_fields .iter() .map(|f| { ( f.ident.clone().unwrap(), extract_type_from_option(&f.ty).unwrap(), ) }) .unzip();
解决方案
数组分区+原地修改场景
你的示例代码问题在于iter_mut()生成的迭代器元素是&mut usize,而partition的闭包参数是该元素的引用(即&&mut usize),必须解引用才能修改内部值。可以直接在闭包中完成解引用和修改,无需后续map操作:
let arr = vec![1, 2, 3, 4, 5, 6]; let (even, odd): (Vec<_>, Vec<_>) = arr.iter_mut().partition(|num| { // 解引用得到&mut usize,再判断并修改 let num_ref = *num; if *num_ref % 2 == 0 { *num_ref *= 2; true } else { false } }); // 验证结果 println!("even: {:?}", even); // 输出 [4, 8, 12] println!("odd: {:?}", odd); // 输出 [1, 3, 5]
或者更简洁的双重解引用写法:
let (even, odd): (Vec<_>, Vec<_>) = arr.iter_mut().partition(|num| { if **num % 2 == 0 { **num *= 2; true } else { false } });
Builder派生场景(避免多次遍历)
你当前的分步操作需要三次遍历(分区+两次映射),可以通过一次遍历完成分区和转换,降低开销。这里提供两种实现方式:
方式1:自定义迭代器适配器
实现一个partition_map适配器,一次性处理分区和转换逻辑:
use std::iter::Iterator; // 自定义迭代器扩展 trait trait PartitionMap: Iterator { fn partition_map<B, C, F>(self, mut f: F) -> (Vec<B>, Vec<C>) where F: FnMut(Self::Item) -> Result<B, C>, { let mut required = Vec::new(); let mut optional = Vec::new(); for item in self { match f(item) { Ok(b) => required.push(b), Err(c) => optional.push(c), } } (required, optional) } } // 为所有迭代器实现该 trait impl<I: Iterator> PartitionMap for I {}
然后用这个适配器替代原分步代码:
let (required_pairs, optional_pairs): (Vec<_>, Vec<_>) = fields.into_iter().partition_map(|f| { let ident = f.ident.clone().unwrap(); match extract_type_from_option(&f.ty) { // 必需字段:返回Ok((标识符, 类型)) None => Ok((ident, f.ty.clone())), // 可选字段:返回Err((标识符, 内部类型)) Some(inner_ty) => Err((ident, inner_ty)), } }); // 最后拆分出标识符和类型向量 let (required_fields_ident, required_fields_ty): (Vec<_>, Vec<_>) = required_pairs.into_iter().unzip(); let (optional_fields_ident, optional_fields_ty): (Vec<_>, Vec<_>) = optional_pairs.into_iter().unzip();
方式2:使用fold一次性收集
如果不想自定义trait,可以用fold直接在一次遍历中完成所有操作:
let ((req_idents, req_tys), (opt_idents, opt_tys)) = fields.into_iter().fold( // 初始化两个元组,分别存储必需/可选字段的标识符和类型 ((Vec::new(), Vec::new()), (Vec::new(), Vec::new())), |((mut req_id, mut req_ty), (mut opt_id, mut opt_ty)), f| { let ident = f.ident.clone().unwrap(); if let Some(inner_ty) = extract_type_from_option(&f.ty) { // 可选字段:收集标识符和内部类型 opt_id.push(ident); opt_ty.push(inner_ty); } else { // 必需字段:收集标识符和原类型 req_id.push(ident); req_ty.push(f.ty.clone()); } ((req_id, req_ty), (opt_id, opt_ty)) }, ); // 直接使用变量:req_idents, req_tys, opt_idents, opt_tys
以上两种方式都只需要遍历fields一次,避免了多次遍历带来的额外开销。
内容的提问来源于stack exchange,提问作者Campbell He
相关产品推荐
相关产品推荐

