We'll build a task management application using Rust in this exciting milestone project. This project will encompass concepts covered in the previous chapters, including structuring your code, error handling, file I/O, and more. Following these steps, you'll create a functional task manager allowing users to add, list, and mark tasks as completed.
Note: The complete implementation is done at the end, but you are given free rein to use your newly acquired knowledge to implement the functions.
Step 1: Project Setup
Create a new Rust project using Cargo:
cargo new task_manager
Navigate to the project directory:
cd task_manager
Step 2: Define Task Struct
Open the
src/main.rs
file and define aTask
struct:struct Task { id: u32, title: String, completed: bool, }
Step 3: Implement Basic Functions
Inside the
main
function, create an empty vector to store tasks:fn main() { let mut tasks: Vec<Task> = Vec::new(); // ... }
Implement a function to add tasks:
fn add_task(tasks: &mut Vec<Task>, title: &str) { let id = (tasks.len() + 1) as u32; let task = Task { id, title: title.to_string(), completed: false }; tasks.push(task); }
Implement a function to list tasks:
fn list_tasks(tasks: &[Task]) { for task in tasks { let status = if task.completed { "[X]" } else { "[ ]" }; println!("{} {}: {}", status, task.id, task.title); } }
Implement a function to mark tasks as completed:
fn complete_task(tasks: &mut Vec<Task>, id: u32) -> Result<(), String> { if let Some(task) = tasks.iter_mut().find(|t| t.id == id) { task.completed = true; Ok(()) } else { Err("Task not found".to_string()) } }
Step 4: Implement File I/O
Create a
tasks.txt
file in the project directory with task data.Implement a function to read tasks from the file:
fn read_tasks_from_file(file_path: &str) -> Result<Vec<Task>, std::io::Error> { // Read tasks from the file and return them as a Vec<Task> }
Implement a function to write tasks to the file:
fn write_tasks_to_file(file_path: &str, tasks: &[Task]) -> Result<(), std::io::Error> { // Write tasks to the file }
Step 5: Integrate Error Handling
Update functions to return
Result
with custom error types.Implement the
main
function:fn main() -> Result<(), Box<dyn std::error::Error>> { // Load tasks from the file let tasks = read_tasks_from_file("tasks.txt")?; // ... // Save tasks to the file write_tasks_to_file("tasks.txt", &tasks)?; Ok(()) }
Step 6: User Interaction
Implement a simple command-line interface using the
std::env
module to handle user input.Parse user commands to add, list, and mark tasks as completed.
Step 7: Run and Test
Run the project using Cargo:
cargo run
Test your task manager by adding, listing, and completing tasks using the command-line interface.
Step 8: Enhancements
Consider enhancing the project by adding more features like editing, deleting, and prioritizing tasks.
Explore additional Rust libraries, like colored terminal output or interactive input libraries, to improve the user experience.
Congratulations! You've successfully built a task management application using Rust. This milestone project combines concepts learned throughout the tutorial to create a practical and functional application. Keep exploring Rust and applying these concepts to more ambitious projects.
Happy coding!
Appendix
Here's the complete code for the task management application, as described in the milestone project:
use std::fs;
use std::io::{Read, Write};
use std::env;
use serde_json
struct Task {
id: u32,
title: String,
completed: bool,
}
fn add_task(tasks: &mut Vec<Task>, title: &str) {
let id = (tasks.len() + 1) as u32;
let task = Task {
id,
title: title.to_string(),
completed: false,
};
tasks.push(task);
}
fn list_tasks(tasks: &[Task]) {
for task in tasks {
let status = if task.completed { "[X]" } else { "[ ]" };
println!("{} {}: {}", status, task.id, task.title);
}
}
fn complete_task(tasks: &mut Vec<Task>, id: u32) -> Result<(), String> {
if let Some(task) = tasks.iter_mut().find(|t| t.id == id) {
task.completed = true;
Ok(())
} else {
Err("Task not found".to_string())
}
}
fn read_tasks_from_file(file_path: &str) -> Result<Vec<Task>, std::io::Error> {
let mut content = String::new();
let mut file = fs::File::open(file_path)?;
file.read_to_string(&mut content)?;
let tasks: Vec<Task> = serde_json::from_str(&content)?;
Ok(tasks)
}
fn write_tasks_to_file(file_path: &str, tasks: &[Task]) -> Result<(), std::io::Error> {
let json_data = serde_json::to_string(tasks)?;
let mut file = fs::File::create(file_path)?;
file.write_all(json_data.as_bytes())?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
let file_path = "tasks.txt";
let mut tasks = if fs::metadata(file_path).is_ok() {
read_tasks_from_file(file_path)?
} else {
Vec::new()
};
match args.get(1).map(|arg| arg.as_str()) {
Some("add") => {
let title = args.get(2).ok_or("Title not provided")?;
add_task(&mut tasks, title);
}
Some("list") => {
list_tasks(&tasks);
}
Some("complete") => {
let id = args
.get(2)
.ok_or("Task ID not provided")?
.parse::<u32>()
.map_err(|_| "Invalid task ID")?;
complete_task(&mut tasks, id)?;
}
_ => {
println!("Usage:");
println!("task_manager add <title>");
println!("task_manager list");
println!("task_manager complete <task_id>");
}
}
write_tasks_to_file(file_path, &tasks)?;
Ok(())
}
To run this code, follow these steps:
Install the
serde
andserde_json
crates by adding the following dependencies to yourCargo.toml
file:
[dependencies]
serde = "1.0"
serde_json = "1.0"
Save the above code to the
src/main.rs
file within yourtask_manager
project directory.Run the project using Cargo:
cargo run add "Buy groceries" cargo run add "Complete project" cargo run list cargo run complete 1
This code demonstrates the complete task management application, including file I/O, error handling, and user interaction. Adjust the code as needed and explore enhancements to make the application more robust and user-friendly.