Stuck with borrowing rules error

Hi,

I'm trying to build a todo app to learn Rust, but I'm a bit stuck.

Currently I have a todo_list struct that holds all todos, and I also operate on that one to create or delete items.

However, since I want to display daily, weekly, monthly and yearly todos later in different sections on the screens, I have getters for each one respectively.

Now having the code as below, I run into errors with the borrowing rules. Since I have a mutable borrow on the todo_list variable so I can add and delete items, I can't add another borrow for my specific getters such as daily_todos.

I understand the rules that I can have as many immutable borrows, but as soon as I have a mutable one that's is, this is the one and only. But now I can't think of how to make this work. I'm starting to think that my approach is flawed from the beginning regarding the data structure, but I wanted to ask for some help here first because I really am stuck.

#[derive(Debug, PartialEq)]
pub enum TodoType {
    Daily,
    Weekly,
}

#[derive(Debug, PartialEq)]
pub struct TodoItem {
    id: String,
    name: String,
    completed: bool,
    comment: Option<String>,
    todo_type: TodoType,
}

#[derive(Debug, Default)]
pub struct TodoList {
    todos: Vec<TodoItem>,
}

impl TodoItem {
    fn new(item: String, comment: Option<String>, todo_type: TodoType) -> Self {
        Self {
            id: "1".to_string(),
            name: item,
            completed: false,
            comment,
            todo_type,
        }
    }
}

impl TodoList {
    pub fn new() -> Self {
        Self { todos: vec![] }
    }

    // Getters
    pub fn get_daily(&self) -> Vec<&TodoItem> {
        self.get_todos(TodoType::Daily)
    }

    pub fn get_weekly(&self) -> Vec<&TodoItem> {
        self.get_todos(TodoType::Weekly)
    }

    fn get_todos(&self, todo_type: TodoType) -> Vec<&TodoItem> {
        self.todos
            .iter()
            .filter(|todo| todo.todo_type == todo_type)
            .collect()
    }

    // Adders
    pub fn add_daily_todo(&mut self, item: String, comment: Option<String>) {
        self.add_todo(item, comment, TodoType::Daily)
    }

    pub fn add_weekly_todo(&mut self, item: String, comment: Option<String>) {
        self.add_todo(item, comment, TodoType::Weekly)
    }

    fn add_todo(&mut self, item: String, comment: Option<String>, todo_type: TodoType) {
        self.todos.push(TodoItem::new(item, comment, todo_type))
    }

    // Removers
    pub fn remove_todo_by_id(&mut self, id: &String) {
        self.todos.retain_mut(|item| item.id != *id)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn can_delete_a_daily_todo() {
        let mut todo_list = TodoList::new();

        todo_list.add_daily_todo("D1".to_string(), None);
        todo_list.add_daily_todo("D2".to_string(), None);

        let daily_todos = todo_list.get_daily();

        // check initial state
        assert_eq!(daily_todos.len(), 2);
        assert_eq!(daily_todos[0].name, "D1".to_owned());
        assert_eq!(daily_todos[1].name, "D2".to_owned());

        // delete first todo
        todo_list.remove_todo_by_id(&daily_todos[0].id);

        // check updated todo list
        assert_eq!(daily_todos.len(), 1);
        assert_eq!(daily_todos[0].name, "D2".to_owned());
    }
}

This gives me the following error on the test:

But I can't change the get_daily to a mutable borrow, since then it complains about
cannot borrow todo_list as mutable more than once at a time

Would appreciate some help here :slight_smile:

Without mass refactoring your code, you should just call get_daily again.

With current code, ignoring daily_todos does not borrow todo_list, it's still a disjoint Vec that will not magically shrink when something happens to todo_list. i.e. Even if you translate your code to python, it still won't work as expected.

1 Like

Thank you for your reply.

Hmm yeah I didn't think of that. It looks like a big refactor is in order.

What would be the smartest way to set this up? I'm thinking having separate structs for daily and weekly todos?

It depends. There's no definitive answer. Since you are just learning Rust, I think calling get_daily again is not that bad. You can return Vec<TodoItem> by cloning the items. I suggest avoid "complicated" stuff at first.

In the end, what's the best pattern depends on your api usage pattern.

Thanks! I will try that.

I will most likely try a few different solutions side by side to see which one works the best.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.