use std::io; struct Todo { id: i16, title: String, completed: bool, deleted: bool, } fn add_todo(todos: &mut Vec, title: &str) { // The size of the vector + 1 makes a decent enough id let new_id = todos.len() as i16 + 1; todos.push(Todo { id: new_id, title: title.to_string(), completed: false, deleted: false, }); } fn remove_todo(todos: &mut Vec, todo_id: i16) { if let Some(todo) = todos.iter_mut().find(|todo| todo.id == todo_id) { todo.deleted = true; } } fn mark_done(todos: &mut Vec, todo_id: i16) { if let Some(todo) = todos.iter_mut().find(|todo| todo.id == todo_id) { todo.completed = true; } } fn print_todos(todos: &Vec) { println!("\n\nTodo List:\n--------------------"); for todo in todos { if !todo.deleted { let done = if todo.completed { "✔" } else { " " }; println!("[{}] {} {}", done, todo.id, todo.title); } } } fn invalid_command(command: &str) { println!("Invalid command: '{}'", command); } fn main() { let mut todos: Vec = Vec::new(); // Print the todo list on start up print_todos(&todos); // Infinite loop loop { // Assign input lines to the `command` variable let mut command = String::new(); io::stdin() .read_line(&mut command) .expect("failed to read line"); // Split input on whitespace let command_parts: Vec<&str> = command.split_whitespace().collect(); // Now match the size of the vector holding the separate words in the command match command_parts.len() { // If 0 we can't really do much 0 => invalid_command(&command), // If the length is 1 it's a `list` command, or an invalid command 1 => match command_parts[0] { "list" => print_todos(&todos), "quit" => break, "exit" => break, _ => invalid_command(&command), }, // If the length is bigger than 1 we look for an `add x x x`, `remove x x x`, or `done x` _ => { // Match the first word in the command match command_parts[0] { // If add, let's join up all words except the first one as our todo title // `[1..]` means from index 1 in the vector to the end "add" => add_todo(&mut todos, &command_parts[1..].join(" ")), // For remove and done we want to send in a todo_id, // so we parse the string as an integer // parse returns a `Result` that is either `Ok` or `Err`, // so we can handle it much as an `Option` return "remove" => if let Ok(num) = command_parts[1].parse::() { remove_todo(&mut todos, num) }, "done" => if let Ok(num) = command_parts[1].parse::() { mark_done(&mut todos, num) }, _ => invalid_command(&command), } }, } // At the end of each loop print the list print_todos(&todos); } }