长安的 Rust 入门教程长安的 Rust 入门教程
首页
基础教程
进阶内容
Rust 官网
编程指南
首页
基础教程
进阶内容
Rust 官网
编程指南
  • 基础教程

    • 📚 基础教程
    • 第1章 - 认识 Rust
    • 第2章 - 安装 Rust
    • 第3章 - Hello World
    • 第4章 - 变量与可变性
    • 第5章 - 数据类型
    • /guide/06-functions.html
    • 第7章 - 控制流
    • 第8章 - 所有权
    • 第9章 - 引用与借用
    • 第10章 - 结构体
    • 第11章 - 枚举
    • 第12章 - 模式匹配
    • 第13章 - 错误处理
    • 第14章 - 集合类型
    • 第15章 - 模块系统
  • 实战项目

    • 第16章 - 实战项目:猜数字游戏
    • 第17章 - 实战项目:待办事项 CLI
    • 第18章 - 实战项目:简单 HTTP 服务器

第17章 - 实战项目:待办事项 CLI

嗨,朋友!我是长安。

这一章我们要做一个实用的命令行工具——待办事项管理器(Todo CLI)。

这个项目会用到:文件操作、结构体、错误处理、JSON 序列化等知识。

🎯 项目目标

制作一个命令行待办事项工具,支持:

  • ✅ 添加任务
  • ✅ 列出所有任务
  • ✅ 完成任务
  • ✅ 删除任务
  • ✅ 数据持久化(保存到文件)

🚀 创建项目

cargo new todo_cli
cd todo_cli

📦 添加依赖

编辑 Cargo.toml:

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
  • serde:序列化/反序列化库
  • serde_json:JSON 格式支持

💻 完整代码

创建 src/main.rs:

use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::io::{self, Write};

#[derive(Debug, Serialize, Deserialize, Clone)]
struct Task {
    id: usize,
    title: String,
    completed: bool,
}

struct TodoList {
    tasks: Vec<Task>,
    file_path: String,
}

impl TodoList {
    fn new(file_path: &str) -> Self {
        let tasks = Self::load_from_file(file_path);
        TodoList {
            tasks,
            file_path: file_path.to_string(),
        }
    }

    fn load_from_file(file_path: &str) -> Vec<Task> {
        match fs::read_to_string(file_path) {
            Ok(content) => serde_json::from_str(&content).unwrap_or_else(|_| Vec::new()),
            Err(_) => Vec::new(),
        }
    }

    fn save_to_file(&self) {
        let json = serde_json::to_string_pretty(&self.tasks).unwrap();
        fs::write(&self.file_path, json).expect("无法保存文件");
    }

    fn add_task(&mut self, title: String) {
        let id = if self.tasks.is_empty() {
            1
        } else {
            self.tasks.iter().map(|t| t.id).max().unwrap() + 1
        };

        let task = Task {
            id,
            title,
            completed: false,
        };

        self.tasks.push(task);
        self.save_to_file();
        println!("✅ 任务已添加!ID: {}", id);
    }

    fn list_tasks(&self) {
        if self.tasks.is_empty() {
            println!("📝 暂无任务");
            return;
        }

        println!("\n📋 待办事项列表:");
        println!("{:-<50}", "");
        for task in &self.tasks {
            let status = if task.completed { "✓" } else { " " };
            println!("[{}] {} - {}", status, task.id, task.title);
        }
        println!("{:-<50}\n", "");
    }

    fn complete_task(&mut self, id: usize) {
        if let Some(task) = self.tasks.iter_mut().find(|t| t.id == id) {
            task.completed = true;
            self.save_to_file();
            println!("🎉 任务 {} 已完成!", id);
        } else {
            println!("❌ 找不到 ID 为 {} 的任务", id);
        }
    }

    fn delete_task(&mut self, id: usize) {
        let len_before = self.tasks.len();
        self.tasks.retain(|t| t.id != id);

        if self.tasks.len() < len_before {
            self.save_to_file();
            println!("🗑️  任务 {} 已删除", id);
        } else {
            println!("❌ 找不到 ID 为 {} 的任务", id);
        }
    }
}

fn print_help() {
    println!("\n📖 待办事项管理器");
    println!("用法:");
    println!("  todo add <任务内容>    - 添加新任务");
    println!("  todo list              - 列出所有任务");
    println!("  todo done <ID>         - 完成任务");
    println!("  todo delete <ID>       - 删除任务");
    println!("  todo help              - 显示帮助\n");
}

fn main() {
    let args: Vec<String> = env::args().collect();

    if args.len() < 2 {
        print_help();
        return;
    }

    let mut todo_list = TodoList::new("tasks.json");

    match args[1].as_str() {
        "add" => {
            if args.len() < 3 {
                println!("❌ 请提供任务内容");
                println!("示例: todo add 买菜");
                return;
            }
            let title = args[2..].join(" ");
            todo_list.add_task(title);
        }
        "list" => {
            todo_list.list_tasks();
        }
        "done" => {
            if args.len() < 3 {
                println!("❌ 请提供任务 ID");
                println!("示例: todo done 1");
                return;
            }
            match args[2].parse::<usize>() {
                Ok(id) => todo_list.complete_task(id),
                Err(_) => println!("❌ 无效的 ID"),
            }
        }
        "delete" => {
            if args.len() < 3 {
                println!("❌ 请提供任务 ID");
                println!("示例: todo delete 1");
                return;
            }
            match args[2].parse::<usize>() {
                Ok(id) => todo_list.delete_task(id),
                Err(_) => println!("❌ 无效的 ID"),
            }
        }
        "help" => {
            print_help();
        }
        _ => {
            println!("❌ 未知命令: {}", args[1]);
            print_help();
        }
    }
}

📖 代码详解

1. 任务结构体

#[derive(Debug, Serialize, Deserialize, Clone)]
struct Task {
    id: usize,
    title: String,
    completed: bool,
}
  • Serialize 和 Deserialize:支持 JSON 序列化
  • id:任务唯一标识
  • completed:完成状态

2. 文件操作

fn load_from_file(file_path: &str) -> Vec<Task> {
    match fs::read_to_string(file_path) {
        Ok(content) => serde_json::from_str(&content).unwrap_or_else(|_| Vec::new()),
        Err(_) => Vec::new(),
    }
}

fn save_to_file(&self) {
    let json = serde_json::to_string_pretty(&self.tasks).unwrap();
    fs::write(&self.file_path, json).expect("无法保存文件");
}
  • 读取 JSON 文件并解析为任务列表
  • 将任务列表序列化为 JSON 并保存

3. 命令解析

let args: Vec<String> = env::args().collect();
match args[1].as_str() {
    "add" => { /* ... */ },
    "list" => { /* ... */ },
    // ...
}

使用 env::args() 获取命令行参数。

🏃 使用示例

构建项目

cargo build --release

运行命令

# 添加任务
cargo run -- add 学习 Rust
cargo run -- add 写代码
cargo run -- add 锻炼身体

# 列出任务
cargo run -- list

# 完成任务
cargo run -- done 1

# 删除任务
cargo run -- delete 2

# 查看帮助
cargo run -- help

输出示例

$ cargo run -- add 学习 Rust
✅ 任务已添加!ID: 1

$ cargo run -- add 写代码
✅ 任务已添加!ID: 2

$ cargo run -- list

📋 待办事项列表:
--------------------------------------------------
[ ] 1 - 学习 Rust
[ ] 2 - 写代码
--------------------------------------------------

$ cargo run -- done 1
🎉 任务 1 已完成!

$ cargo run -- list

📋 待办事项列表:
--------------------------------------------------
[✓] 1 - 学习 Rust
[ ] 2 - 写代码
--------------------------------------------------

📁 数据文件

任务保存在 tasks.json 文件中:

[
  {
    "id": 1,
    "title": "学习 Rust",
    "completed": true
  },
  {
    "id": 2,
    "title": "写代码",
    "completed": false
  }
]

🎨 改进建议

1. 添加优先级

#[derive(Debug, Serialize, Deserialize, Clone)]
enum Priority {
    Low,
    Medium,
    High,
}

struct Task {
    id: usize,
    title: String,
    completed: bool,
    priority: Priority,  // 新增
}

2. 添加截止日期

use chrono::{NaiveDate, Local};

struct Task {
    id: usize,
    title: String,
    completed: bool,
    due_date: Option<NaiveDate>,  // 新增
}

3. 搜索功能

fn search_tasks(&self, keyword: &str) {
    let results: Vec<&Task> = self.tasks
        .iter()
        .filter(|t| t.title.contains(keyword))
        .collect();
    
    for task in results {
        println!("[{}] {} - {}", task.id, task.title, task.completed);
    }
}

4. 统计功能

fn show_stats(&self) {
    let total = self.tasks.len();
    let completed = self.tasks.iter().filter(|t| t.completed).count();
    let pending = total - completed;
    
    println!("📊 统计信息:");
    println!("  总任务:{}", total);
    println!("  已完成:{}", completed);
    println!("  待完成:{}", pending);
}

💡 学到的知识点

通过这个项目,我们学会了:

  • ✅ 结构体和实现(struct 和 impl)
  • ✅ 文件 I/O 操作
  • ✅ JSON 序列化/反序列化
  • ✅ 命令行参数处理
  • ✅ 向量操作(Vec)
  • ✅ 模式匹配
  • ✅ 错误处理

💪 挑战任务

  1. 添加任务分类功能(工作、生活、学习等)
  2. 支持任务编辑功能
  3. 添加任务导出功能(导出为 CSV 或 Markdown)
  4. 制作交互式 UI(使用 crossterm 或 tui-rs)
  5. 添加任务提醒功能
任务分类示例
#[derive(Debug, Serialize, Deserialize, Clone)]
enum Category {
    Work,
    Personal,
    Study,
}

struct Task {
    id: usize,
    title: String,
    completed: bool,
    category: Category,
}

// 按分类列出
fn list_by_category(&self, category: Category) {
    let tasks: Vec<&Task> = self.tasks
        .iter()
        .filter(|t| matches!(t.category, category))
        .collect();
    // 打印任务...
}

🚀 下一步

完成这个项目后,你可以:

  1. 发布到 crates.io
  2. 制作安装脚本
  3. 添加更多功能

第18章 - 实战项目:简单 HTTP 服务器 →

最近更新: 2025/12/26 18:01
Contributors: 王长安
Prev
第16章 - 实战项目:猜数字游戏
Next
第18章 - 实战项目:简单 HTTP 服务器