Search code examples
rustborrow-checker

How to iterate over a vec in Rust and make changes?


I'm trying to build a task manager with Rust for practice.

I want to have an archived tasks sections, and so this is my task manager struct:

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

I have functions to make tasks and archived tasks public:

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
    }
}

Then, I have a function to archive a task that works:

impl TaskManager {
    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())
        }
    }
}

And then... the borrow checker. I can't do this or anything like it, so I can't iterate over my tasks and call archive_task:

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);
            }
        }
    }
}

Of course the compiler complains:

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

Is there something I'm missing or what I'm trying to do is not possible?


Solution

  • Instead of using index and allocating memory for them, I propose another solution using iterators:

    /// This will require nightly channel
    #![feature(extract_if)]
    struct Task {
        done: bool
    }
    
    impl Task {
        pub fn done(&self) -> bool {self.done}
    }
    
    pub struct TaskManager {
        tasks: Vec<Task>,
        archived_tasks: Vec<Task>,
    }
    
    impl TaskManager {
        pub fn archive_done_tasks(&mut self) {
            let iter = self.tasks.extract_if(|x| x.done());
            self.archived_tasks.extend(iter);
        }
    }
    
    struct App {
        task_manager: TaskManager,
    }
    
    impl App {
        fn archive_done_tasks(&mut self) {
            self.task_manager.archive_done_tasks()
        }
    }
    
    
    fn main() {
        let mut app = App{task_manager: TaskManager{tasks: vec![], archived_tasks: vec![]}};
        app.archive_done_tasks();
        println!("Done")
    }
    

    If you want to use stable channel you can do its equivalent:

    /// Stable channel
    struct Task {
        done: bool
    }
    
    impl Task {
        pub fn done(&self) -> bool {self.done}
    }
    
    pub struct TaskManager {
        tasks: Vec<Task>,
        archived_tasks: Vec<Task>,
    }
    
    impl TaskManager {
        pub fn archive_done_tasks(&mut self) {
            let mut i = 0;
            while i < self.tasks.len() {
                if self.tasks[i].done() {
                    let val = self.tasks.remove(i);
                    self.archived_tasks.push(val);
                } else {
                    i += 1;
                }
            }
        }
    }
    
    struct App {
        task_manager: TaskManager,
    }
    
    impl App {
        fn archive_done_tasks(&mut self) {
            self.task_manager.archive_done_tasks()
        }
    }
    
    
    fn main() {
        let mut app = App{task_manager: TaskManager{tasks: vec![], archived_tasks: vec![]}};
        app.archive_done_tasks();
        println!("Done")
    }