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

Rust遍历Vec并修改时触发借用检查器报错,如何解决?

问题分析与解决方案

问题背景

练习用Rust构建任务管理器,定义了包含普通任务和归档任务的TaskManager结构体:

pub struct TaskManager {
    tasks: Vec<Task>,
    archived_tasks: Vec<Task>,
}

并实现了访问器方法和归档单个任务的方法:

impl TaskManager {
    pub fn tasks(&self) -> &Vec<Task> {
        &self.tasks
    }

    pub fn tasks_mut(&mut self) -> &mut Vec<Task> {
        &mut self.tasks
    }

    pub fn archived_tasks(&self) -> &Vec<Task> {
        &self.archived_tasks
    }

    pub fn archived_tasks_mut(&mut self) -> &mut Vec<Task> {
        &mut self.archived_tasks
    }

    pub fn archive_task(&mut self, idx: usize) -> Result<(), String> {
        match self.tasks.get(idx) {
            Some(_) => {
                self.archived_tasks.push(self.tasks.remove(idx));
                Ok(())
            },
            None => Err("No task with that index".to_string())
        }
    }
}

但在遍历任务并归档完成任务时遇到借用检查错误:

impl App {
    fn archive_done_tasks(&mut self) {
        for (idx, task) in self.task_manager.tasks().iter().enumerate() {
            if task.done() {
                self.task_manager.archive_task(idx);
            }
        }
    }
}

编译器报错:

error[E0502]: cannot borrow `self.task_manager` as mutable because it is also borrowed as immutable
   --> src/app.rs:194:17
    |
192 |         for (idx, task) in self.task_manager.tasks().iter().enumerate() {
    |                            --------------------------------------------
    |                            |
    |                            immutable borrow occurs here
    |                            immutable borrow later used here
193 |             if task.done() {
194 |                 self.task_manager.archive_task(idx);
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

错误原因

  1. 借用规则冲突:遍历tasks()时获取了TaskManager的不可变引用,这个引用在整个循环周期内有效;而调用archive_task需要获取TaskManager的可变引用,Rust不允许同一时间存在可变和不可变引用。
  2. 索引失效问题:即使绕过借用检查,调用tasks.remove(idx)会改变向量的长度和元素位置,后续迭代器会访问错误的元素,甚至引发panic。

解决方案

方案1:先收集索引再倒序处理

先一次性收集所有需要归档的任务索引,再倒序遍历索引执行归档操作(倒序可以避免remove导致的索引偏移):

impl App {
    fn archive_done_tasks(&mut self) {
        // 收集所有已完成任务的索引
        let done_indices: Vec<usize> = self.task_manager.tasks()
            .iter()
            .enumerate()
            .filter(|(_, task)| task.done())
            .map(|(idx, _)| idx)
            .collect();
        
        // 倒序处理索引,防止remove后后续索引失效
        for &idx in done_indices.iter().rev() {
            if let Err(e) = self.task_manager.archive_task(idx) {
                eprintln!("Failed to archive task: {}", e);
            }
        }
    }
}

方案2:在TaskManager内部实现批量归档

直接在TaskManager中添加批量归档完成任务的方法,内部处理可以避免外部的借用冲突:

impl TaskManager {
    pub fn archive_done_tasks(&mut self) {
        // 将tasks分割为未完成和已完成的任务
        let (remaining_tasks, done_tasks): (Vec<Task>, Vec<Task>) = self.tasks.drain(..)
            .partition(|task| !task.done());
        
        // 更新任务列表,将已完成任务加入归档
        self.tasks = remaining_tasks;
        self.archived_tasks.extend(done_tasks);
    }
}

然后在App中直接调用:

impl App {
    fn archive_done_tasks(&mut self) {
        self.task_manager.archive_done_tasks();
    }
}

这个方案更简洁高效,drain(..)清空原向量并返回所有元素,partition一次性分割出两类任务,避免了多次remove的开销和索引问题。

内容的提问来源于stack exchange,提问作者Rulus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 17:01:13