
从零到一:构建一个完整的 Rust 应用
学了这么多 Rust 知识,是时候做个完整的项目了!
今天咱们从零开始,构建一个任务管理 CLI 工具,名字叫 taskmaster。
功能清单:
用到的知识:
taskmaster/
├── Cargo.toml
├── src/
│ ├── main.rs # 入口
│ ├── lib.rs # 库代码
│ ├── task.rs # 任务定义
│ ├── storage.rs # 数据存储
│ └── commands.rs # 命令处理
├── tests/
│ └── integration.rs # 集成测试
└── README.md
cargo new taskmaster
cd taskmaster
# 添加依赖
cargo add clap --features derive
cargo add serde --features derive
cargo add serde_json
cargo add thiserror
cargo add colored
cargo add dirs
// src/task.rs
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
pub id: u32,
pub title: String,
pub description: Option<String>,
pub completed: bool,
pub created_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
}
impl Task {
pub fn new(id: u32, title: String, description: Option<String>) -> Self {
Task {
id,
title,
description,
completed: false,
created_at: Utc::now(),
completed_at: None,
}
}
pub fn complete(&mut self) {
self.completed = true;
self.completed_at = Some(Utc::now());
}
}
#[derive(Debug, Clone)]
pub enum Priority {
Low,
Medium,
High,
}
// src/storage.rs
use std::fs;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::task::Task;
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct TaskStore {
tasks: Vec<Task>,
next_id: u32,
}
impl TaskStore {
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
let path = Self::get_path()?;
if !path.exists() {
return Ok(TaskStore::default());
}
let content = fs::read_to_string(path)?;
let store: TaskStore = serde_json::from_str(&content)?;
Ok(store)
}
pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> {
let path = Self::get_path()?;
let content = serde_json::to_string_pretty(self)?;
fs::write(path, content)?;
Ok(())
}
pub fn add_task(&mut self, title: String, description: Option<String>) -> &Task {
let task = Task::new(self.next_id, title, description);
self.next_id += ;
self.tasks.push(task);
self.tasks.last().unwrap()
}
pub fn list_tasks(&self) -> &[Task] {
&self.tasks
}
pub fn complete_task(&mut self, id: u32) -> Option<&Task> {
self.tasks.iter_mut()
.find(|t| t.id == id)
.map(|task| {
task.complete();
task
})
}
pub fn delete_task(&mut self, id: u32) -> Option<Task> {
if let Some(pos) = self.tasks.iter().position(|t| t.id == id) {
Some(self.tasks.remove(pos))
} else {
None
}
}
fn get_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
let config_dir = dirs::config_dir()
.ok_or("无法找到配置目录")?;
Ok(config_dir.join("taskmaster").join("tasks.json"))
}
}
// src/commands.rs
use colored::Colorize;
use crate::storage::TaskStore;
pub fn add(store: &mut TaskStore, title: String, description: Option<String>) {
let task = store.add_task(title, description);
println!("✅ 已添加任务 #{}", task.id.to_string().green());
}
pub fn list(store: &TaskStore) {
let tasks = store.list_tasks();
if tasks.is_empty() {
println!("{}", "暂无任务".yellow());
return;
}
println!("\n{}", "📋 任务列表".bold());
println!("{}", "=".repeat());
for task in tasks {
let status = if task.completed {
"✓".green()
} else {
"○".yellow()
};
println!("{} #{} {}", status, task.id.to_string().bold(), task.title);
if let Some(desc) = &task.description {
println!(" {}", desc.dimmed());
}
println!(" 创建时间:{}", task.created_at.format("%Y-%m-%d %H:%M"));
if task.completed {
println!(" {}", "✓ 已完成".green());
}
println!();
}
}
pub fn complete(store: &mut TaskStore, id: u32) {
match store.complete_task(id) {
Some(task) => println!("✅ 任务 #{} 已完成!", task.id.to_string().green()),
None => println!("{}", format!("❌ 未找到任务 #{}", id).red()),
}
}
pub fn delete(store: &mut TaskStore, id: u32) {
match store.delete_task(id) {
Some(task) => println!("✅ 已删除任务 #{}: {}", task.id.to_string().green(), task.title),
None => println!("{}", format!("❌ 未找到任务 #{}", id).red()),
}
}
// src/main.rs
use clap::{Parser, Subcommand};
use taskmaster::{storage::TaskStore, commands};
#[derive(Parser)]
#[command(name = "taskmaster")]
#[command(about = "一个强大的任务管理工具", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// 添加新任务
Add {
/// 任务标题
#[arg(short, long)]
title: String,
/// 任务描述
#[arg(short, long)]
description: Option<String>,
},
/// 列出所有任务
List,
/// 完成任务
Complete {
/// 任务 ID
id: u32,
},
/// 删除任务
Delete {
/// 任务 ID
id: u32,
},
}
fn main() {
let cli = Cli::parse();
// 加载存储
let mut store = match TaskStore::load() {
Ok(store) => store,
Err(e) => {
eprintln!("❌ 加载数据失败:{}", e);
std::process::exit();
}
};
// 执行命令
match cli.command {
Commands::Add { title, description } => {
commands::add(&mut store, title, description);
}
Commands::List => {
commands::list(&store);
}
Commands::Complete { id } => {
commands::complete(&mut store, id);
}
Commands::Delete { id } => {
commands::delete(&mut store, id);
}
}
// 保存数据
if let Err(e) = store.save() {
eprintln!("❌ 保存数据失败:{}", e);
std::process::exit();
}
}
// tests/integration.rs
use taskmaster::storage::TaskStore;
#[test]
fn test_add_task() {
let mut store = TaskStore::default();
let task = store.add_task("测试任务".to_string(), Some("描述".to_string()));
assert_eq!(task.id, );
assert_eq!(task.title, "测试任务");
assert_eq!(store.list_tasks().len(), );
}
#[test]
fn test_complete_task() {
let mut store = TaskStore::default();
store.add_task("任务 1".to_string(), None);
store.complete_task();
let task = &store.list_tasks()[];
assert!(task.completed);
assert!(task.completed_at.is_some());
}
#[test]
fn test_delete_task() {
let mut store = TaskStore::default();
store.add_task("任务 1".to_string(), None);
store.add_task("任务 2".to_string(), None);
store.delete_task();
assert_eq!(store.list_tasks().len(), );
assert_eq!(store.list_tasks()[].id, );
}
#[test]
fn test_persistence() {
let mut store = TaskStore::default();
store.add_task("持久化测试".to_string(), None);
// 保存到临时文件
// ...
}
# Taskmaster
一个用 Rust 编写的命令行任务管理工具。
## 安装
```bash
cargo install --git https://github.com/yourusername/taskmaster
taskmaster add --title "学习 Rust" --description "完成教程"
taskmaster list
taskmaster complete 1
taskmaster delete 1
# 运行
cargo run -- list
# 测试
cargo test
# 构建 release
cargo build --release
## 🐛 常见问题
### 问题 1:依赖冲突
```bash
# 更新依赖
cargo update
# 检查依赖树
cargo tree
// ❌ 硬编码路径
let path = "C:\\Users\\data.json";
// ✅ 用跨平台库
use dirs;
let path = dirs::config_dir().unwrap().join("taskmaster");
// ❌ 到处 unwrap
let file = fs::File::open(path).unwrap();
// ✅ 统一错误类型
use thiserror::Error;
#[derive(Error, Debug)]
pub enum TaskError {
#[error("IO 错误:{0}")]
Io(#[from] std::io::Error),
#[error("JSON 错误:{0}")]
Json(#[from] serde_json::Error),
}
这个项目可以继续扩展:

金句回顾:
系列完结!
恭喜你完成了 Rust 教程系列 50 篇!从入门到实战,你已经掌握了:
接下来:
Rust 的学习之旅没有终点,但你已经有了坚实的开始。加油!🦀