This document tracks how the project evolves chapter by chapter through "The Rust Programming Language" book, and what the final result will look like.
Taskstruct,TodoListstruct wrappingVec<Task>- Methods:
add_task,remove_task,complete_task,list_tasks - Interactive CLI loop using
std::io::stdin,splitn,parse::<usize>()
-
Statusenum (Todo,InProgress,Done,#[derive(Clone, Copy)]), replacingdone: bool -
set_in_progress(index)to move a task toInProgress -
Commandenum with payloads (Add(String),Done(usize),Remove(usize),Progress(usize),List,Quit,Unknown) -
parse_command(input: &str) -> Commandfor input parsing - Main loop is a single
matchonCommand - Bonus:
remove_tasknow bounds-checks instead of panicking (pre-empts part of Ch. 9) - Bonus:
list_taskshandles the empty-list case
- Split into
task.rs(Task + Status),todo_list.rs(TodoList),main.rs(CLI loop + Command + parse_command) - Use
mod/usecorrectly across files
- Switch storage from
Vec<Task>toHashMap<u32, Task>keyed by an auto-incrementing ID (next_idcounter — IDs stay stable across deletions) - Add
list_by_status(&self, status: Status)filtering method - Update
Command::Done/Remove/Progressto use the new ID type (u32)
- Define
enum TodoError { TaskNotFound(u32) }(#[derive(PartialEq, Debug)]) -
complete_task,remove_task,set_in_progressreturnResult<(), TodoError> - Main loop matches on
Resultand prints user-friendly errors - Richer command parsing →
Command::Unrecognized(String)(unknown command name) split fromCommand::InvalidArgument(String)(bad/missing argument), instead of a singleUnknownvariant
- Implement
DisplayforStatus(icons:[],[~],[x]) andTask("{status} {description}") - Optional:
priority: u8field +PartialOrd/Ord— not done, skipped as optional
- Refactor
list_tasks/list_by_statusto returnVec<String>(testable, not just printed) via a sharedprint_lineshelper - Unit tests: add, complete (+ not-found case), remove (+ not-found case), set_in_progress, filter by status
- Evaluated and deemed not applicable: this is a REPL-style interactive
program rather than a one-shot CLI tool like
minigrep, so there's no argument-parsing/logic/I/O split to extract in the same way. Skipped by design, not an oversight.
-
list_by_statusalready used.filter()with a closure (done as part of Ch. 8/11 work) -
list_tasksandlist_by_statusnow collect into aVec<(&u32, &Task)>and.sort_by_key(|(id, _)| **id)before formatting, fixing the previously unorderedHashMapiteration
-
#[derive(Serialize, Deserialize)]onTask,Status, andTodoList -
save_to_file(&self, path: &str) -> io::Result<()>andload_from_file(path: &str) -> io::Result<TodoList> - Load on startup (
TodoList::load_from_file, falling back toTodoList::new()if the file doesn't exist yet), save onCommand::Quit - Round-trip test (save → load → assert) and missing-file test
- Known rough edge, deliberately left as-is for now:
serde_json::Erroris converted toio::Errorvia.map_err(..., io::ErrorKind::Other)insidesave_to_file/load_from_filesince?can't chain the two error types directly. A cleaner fix (a dedicatedAppErrorenum withFromimpls) is a good candidate mini-exercise once comfortable with Ch. 9 patterns again.
- Multiple named lists (e.g. "Work"/"Personal") sharing tasks via
Rc<RefCell<Task>>
- Background thread auto-saving the list to disk every N seconds
(
std::thread+Mutex)
By the end of this roadmap, the project will be a command-line to-do application with:
- Rich task model: each task has a description and a status
(
Todo/InProgress/Done), optionally a priority - Clean architecture: code split across modules (
task,todo_list,main), with a dedicatedCommandenum separating input parsing from business logic - Efficient storage: tasks stored in a
HashMap<u32, Task>for O(1) lookup/removal by ID - Robust error handling: no panics on invalid input — errors are
represented with a custom
TodoErrortype and handled viaResult - Idiomatic formatting & iteration: tasks implement
Display; filtering and sorting use iterator chains with closures - Persistence: tasks are saved to a JSON file and reloaded on startup
- Test suite:
cargo testverifies core behavior, including edge cases - (optional) multiple lists and background auto-save for extra practice with smart pointers and concurrency
The end result is a small but genuinely functional, well-structured Rust CLI application — a solid first portfolio project demonstrating ownership, error handling, modularity, and testing.