You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 23:25:24