Rust 项目实战
概述
本教程将通过一个完整的实战项目,综合运用前面学到的 Rust 知识。我们将构建一个简单的命令行工具——任务管理器(Todo 应用)。
项目规划
功能需求
-
添加任务
-
列出所有任务
-
标记任务完成
-
删除任务
-
将任务保存到文件
-
从文件加载任务
项目结构
todo_app/
├── Cargo.toml
├── src/
│ ├── main.rs
│ ├── task.rs
│ ├── storage.rs
│ └── cli.rs
└── tasks.json
创建项目
初始化项目
cargo new todo_app
cd todo_app
配置 Cargo.toml
[package]
name = "todo_app"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
clap = { version = "3.0", features = ["derive"] }
colored = "2.0"
dirs = "4.0"
实现核心模块
task.rs - 任务模型
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
pub id: u32,
pub description: String,
pub completed: bool,
}
impl Task {
pub fn new(id: u32, description: String) -> Self {
Task {
id,
description,
completed: false,
}
}
pub fn mark_completed(&mut self) {
self.completed = true;
}
}
impl fmt::Display for Task {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let status = if self.completed {
"[✓]"
} else {
"[ ]"
};
write!(f, "{} {}: {}", status, self.id, self.description)
}
}
storage.rs - 存储管理
use crate::task::Task;
use serde_json;
use std::fs;
use std::path::PathBuf;
pub struct Storage {
file_path: PathBuf,
}
impl Storage {
pub fn new(file_path: PathBuf) -> Self {
Storage { file_path }
}
pub fn save_tasks(&self, tasks: &[Task]) -> Result<(), String> {
let json = serde_json::to_string_pretty(tasks)
.map_err(|e| format!("序列化失败: {}", e))?;
fs::write(&self.file_path, json)
.map_err(|e| format!("写入文件失败: {}", e))?;
Ok(())
}
pub fn load_tasks(&self) -> Result<Vec<Task>, String> {
if !self.file_path.exists() {
return Ok(Vec::new());
}
let content = fs::read_to_string(&self.file_path)
.map_err(|e| format!("读取文件失败: {}", e))?;
let tasks: Vec<Task> = serde_json::from_str(&content)
.map_err(|e| format!("反序列化失败: {}", e))?;
Ok(tasks)
}
}
cli.rs - 命令行接口
use clap::{Parser, Subcommand};
use colored::*;
#[derive(Parser)]
#[clap(name = "todo")]
#[clap(about = "一个简单的任务管理器", long_about = None)]
struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// 添加新任务
Add {
/// 任务描述
description: String,
},
/// 列出所有任务
List,
/// 标记任务为完成
Complete {
/// 任务 ID
id: u32,
},
/// 删除任务
Delete {
/// 任务 ID
id: u32,
},
}
pub fn parse_args() -> Commands {
let cli = Cli::parse();
cli.command
}
pub fn print_success(message: &str) {
println!("{}", message.green());
}
pub fn print_error(message: &str) {
eprintln!("{}", message.red());
}
pub fn print_info(message: &str) {
println!("{}", message.blue());
}
main.rs - 主程序
mod task;
mod storage;
mod cli;
use crate::task::Task;
use crate::storage::Storage;
use crate::cli::{parse_args, Commands, print_success, print_error, print_info};
use dirs::home_dir;
use std::path::PathBuf;
struct TodoApp {
tasks: Vec<Task>,
storage: Storage,
}
impl TodoApp {
fn new() -> Result<Self, String> {
let mut data_dir = home_dir().ok_or("无法找到主目录")?;
data_dir.push(".todo");
std::fs::create_dir_all(&data_dir)
.map_err(|e| format!("创建目录失败: {}", e))?;
let file_path = data_dir.join("tasks.json");
let storage = Storage::new(file_path);
let tasks = storage.load_tasks()?;
Ok(TodoApp { tasks, storage })
}
fn add_task(&mut self, description: String) -> Result<(), String> {
let id = self.tasks.len() as u32 + 1;
let task = Task::new(id, description);
self.tasks.push(task);
self.save()?;
print_success(&format!("任务已添加: {}", description));
Ok(())
}
fn list_tasks(&self) {
if self.tasks.is_empty() {
print_info("没有任务");
return;
}
println!("\n任务列表:");
println!("{}", "─".repeat(40));
for task in &self.tasks {
println!("{}", task);
}
println!("{}", "─".repeat(40));
println!("总计: {} 个任务", self.tasks.len());
}
fn complete_task(&mut self, id: u32) -> Result<(), String> {
let task = self.tasks
.iter_mut()
.find(|t| t.id == id)
.ok_or_else(|| format!("任务 ID {} 不存在", id))?;
if task.completed {
print_info(&format!("任务 {} 已经完成", id));
} else {
task.mark_completed();
self.save()?;
print_success(&format!("任务 {} 标记为完成", id));
}
Ok(())
}
fn delete_task(&mut self, id: u32) -> Result<(), String> {
let index = self.tasks
.iter()
.position(|t| t.id == id)
.ok_or_else(|| format!("任务 ID {} 不存在", id))?;
self.tasks.remove(index);
self.save()?;
print_success(&format!("任务 {} 已删除", id));
Ok(())
}
fn save(&self) -> Result<(), String> {
self.storage.save_tasks(&self.tasks)
}
fn run(&mut self) -> Result<(), String> {
let command = parse_args();
match command {
Commands::Add { description } => {
self.add_task(description)?;
}
Commands::List => {
self.list_tasks();
}
Commands::Complete { id } => {
self.complete_task(id)?;
}
Commands::Delete { id } => {
self.delete_task(id)?;
}
}
Ok(())
}
}
fn main() {
match TodoApp::new() {
Ok(mut app) => {
if let Err(e) = app.run() {
print_error(&e);
std::process::exit(1);
}
}
Err(e) => {
print_error(&format!("初始化失败: {}", e));
std::process::exit(1);
}
}
}
测试项目
运行项目
cargo run -- add "学习 Rust"
cargo run -- add "编写代码"
cargo run -- list
cargo run -- complete 1
cargo run -- list
cargo run -- delete 2
cargo run -- list
查看帮助
cargo run -- --help
添加测试
task.rs 测试
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_task() {
let task = Task::new(1, "测试任务".to_string());
assert_eq!(task.id, 1);
assert_eq!(task.description, "测试任务");
assert_eq!(task.completed, false);
}
#[test]
fn test_mark_completed() {
let mut task = Task::new(1, "测试任务".to_string());
task.mark_completed();
assert_eq!(task.completed, true);
}
}
storage.rs 测试
#[cfg(test)]
mod tests {
use super::*;
use tempfile::NamedTempFile;
#[test]
fn test_save_and_load() {
let temp_file = NamedTempFile::new().unwrap();
let storage = Storage::new(temp_file.path().to_path_buf());
let tasks = vec![
Task::new(1, "任务1".to_string()),
Task::new(2, "任务2".to_string()),
];
storage.save_tasks(&tasks).unwrap();
let loaded_tasks = storage.load_tasks().unwrap();
assert_eq!(loaded_tasks.len(), 2);
assert_eq!(loaded_tasks[0].description, "任务1");
assert_eq!(loaded_tasks[1].description, "任务2");
}
}
运行测试
cargo test
添加文档
模块文档
//! 任务管理器
//!
//! 提供任务的创建、修改和显示功能。
use serde::{Deserialize, Serialize};
use std::fmt;
/// 表示一个任务
///
/// # Fields
///
/// * `id` - 任务的唯一标识符
/// * `description` - 任务描述
/// * `completed` - 任务是否完成
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
// ...
}
生成文档
cargo doc --open
优化和改进
添加搜索功能
impl TodoApp {
fn search_tasks(&self, keyword: &str) {
let matching_tasks: Vec<&Task> = self.tasks
.iter()
.filter(|t| t.description.contains(keyword))
.collect();
if matching_tasks.is_empty() {
print_info(&format!("没有找到包含 '{}' 的任务", keyword));
} else {
println!("\n匹配的任务:");
println!("{}", "─".repeat(40));
for task in matching_tasks {
println!("{}", task);
}
}
}
}
添加优先级
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
pub id: u32,
pub description: String,
pub completed: bool,
pub priority: Priority,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Priority {
Low,
Medium,
High,
}
添加截止日期
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
pub id: u32,
pub description: String,
pub completed: bool,
pub due_date: Option<DateTime<Utc>>,
}
构建和发布
构建发布版本
cargo build --release
安装到系统
cargo install --path .
发布到 crates.io
cargo login
cargo publish
总结
本教程通过一个完整的任务管理器项目,展示了如何使用 Rust 构建实际应用:
-
项目规划:
- 定义功能需求
- 设计项目结构
- 配置依赖
-
模块实现:
- Task 模型:定义任务结构和行为
- Storage 模块:处理数据持久化
- CLI 模块:处理命令行参数
- 主程序:协调各个模块
-
测试:
- 单元测试
- 集成测试
- 运行测试
-
文档:
- 添加注释
- 生成文档
-
优化改进:
- 搜索功能
- 优先级
- 截止日期
-
构建发布:
- 构建发布版本
- 安装到系统
- 发布到 crates.io
通过这个项目,你学会了如何:
-
使用 Cargo 管理项目
-
组织代码模块
-
处理错误
-
使用外部库
-
编写测试
-
编写文档
-
构建和发布
这个项目可以作为基础,继续扩展更多功能,如任务分类、提醒、同步等。
下一步
在最后一篇教程中,我们将学习 Rust 的最佳实践,包括:
-
代码风格
-
性能优化
-
安全性
-
可维护性
-
常见陷阱和解决方案
继续学习 Rust,掌握这门强大语言的更多特性!

