欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 名人名企 > Rust从入门到精通之进阶篇:20.项目实践

Rust从入门到精通之进阶篇:20.项目实践

2025/3/30 4:16:53 来源:https://blog.csdn.net/u013985879/article/details/146502723  浏览:    关键词:Rust从入门到精通之进阶篇:20.项目实践

项目实践

在本章中,我们将把前面学到的所有 Rust 知识整合起来,构建一个完整的应用程序。通过实际项目,你将学习如何组织代码、处理依赖关系、实现功能以及测试和部署 Rust 应用程序。我们将构建一个命令行待办事项管理器(Todo CLI),它具有添加、列出、完成和删除任务的功能。

项目规划

在开始编码之前,让我们先规划项目的功能和结构。

功能需求

我们的待办事项管理器应该支持以下功能:

  1. 添加新任务
  2. 列出所有任务(可选按状态过滤)
  3. 将任务标记为已完成
  4. 删除任务
  5. 将任务保存到文件中,并在启动时加载

技术选择

我们将使用以下库来构建应用程序:

  • clap:用于命令行参数解析
  • serdeserde_json:用于 JSON 序列化和反序列化
  • chrono:用于处理日期和时间
  • anyhow:用于错误处理
  • colored:用于终端彩色输出

项目结构

todo-cli/
├── Cargo.toml
├── src/
│   ├── main.rs        # 程序入口点
│   ├── cli.rs         # 命令行接口
│   ├── task.rs        # 任务数据结构
│   └── storage.rs     # 文件存储逻辑
└── README.md

项目初始化

首先,让我们创建一个新的 Rust 项目:

cargo new todo-cli
cd todo-cli

然后,添加所需的依赖项到 Cargo.toml 文件:

[package]
name = "todo-cli"
version = "0.1.0"
edition = "2021"[dependencies]
clap = { version = "4.0", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
anyhow = "1.0"
colored = "2.0"

实现任务数据结构

首先,我们需要定义任务的数据结构。创建 src/task.rs 文件:

use chrono::{DateTime, Local};
use serde::{Deserialize, Serialize};
use std::fmt;
use colored::*;#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Task {pub id: usize,pub description: String,pub completed: bool,pub created_at: DateTime<Local>,
}impl Task {pub fn new(id: usize, description: String) -> Self {Self {id,description,completed: false,created_at: Local::now(),}}pub fn toggle_completion(&mut self) {self.completed = !self.completed;}
}impl fmt::Display for Task {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {let status = if self.completed {"[✓]".green()} else {"[ ]".red()};write!(f,"{} {} - {} ({})",status,self.id.to_string().blue(),self.description,self.created_at.format("%Y-%m-%d %H:%M"))}
}

实现存储逻辑

接下来,我们需要实现任务的存储和加载功能。创建 src/storage.rs 文件:

use crate::task::Task;
use anyhow::{Context, Result};
use std::fs::{self, File};
use std::io::{BufReader, BufWriter};
use std::path::Path;pub struct Storage {file_path: String,tasks: Vec<Task>,next_id: usize,
}impl Storage {pub fn new(file_path: &str) -> Result<Self> {let tasks = Self::load_tasks(file_path)?;let next_id = tasks.iter().map(|task| task.id).max().unwrap_or(0) + 1;Ok(Self {file_path: file_path.to_string(),tasks,next_id,})}fn load_tasks(file_path: &str) -> Result<Vec<Task>> {let path = Path::new(file_path);if !path.exists() {return Ok(Vec::new());}let file = File::open(path).with_context(|| format!("无法打开文件: {}", file_path))?;let reader = BufReader::new(file);let tasks = serde_json::from_reader(reader).with_context(|| format!("无法解析文件: {}", file_path))?;Ok(tasks)}fn save_tasks(&self) -> Result<()> {// 确保目录存在if let Some(parent) = Path::new(&self.file_path).parent() {fs::create_dir_all(parent).with_context(|| format!("无法创建目录: {}", parent.display()))?;}let file = File::create(&self.file_path).with_context(|| format!("无法创建文件: {}", self.file_path))?;let writer = BufWriter::new(file);serde_json::to_writer_pretty(writer, &self.tasks).with_context(|| format!("无法写入文件: {}", self.file_path))?;Ok(())}pub fn add_task(&mut self, description: String) -> Result<&Task> {let task = Task::new(self.next_id, description);self.next_id += 1;self.tasks.push(task);self.save_tasks()?;Ok(self.tasks.last().unwrap())}pub fn list_tasks(&self, show_completed: bool) -> Vec<&Task> {self.tasks.iter().filter(|task| show_completed || !task.completed).collect()}pub fn complete_task(&mut self, id: usize) -> Result<()> {let task = self.tasks.iter_mut().find(|task| task.id == id).with_context(|| format!("未找到 ID 为 {} 的任务", id))?;task.toggle_completion();self.save_tasks()?;Ok(())}pub fn delete_task(&mut self, id: usize) -> Result<Task> {let position = self.tasks.iter().position(|task| task.id == id).with_context(|| format!("未找到 ID 为 {} 的任务", id))?;let task = self.tasks.remove(position);self.save_tasks()?;Ok(task)}
}

实现命令行接口

现在,我们需要实现命令行接口。创建 src/cli.rs 文件:

use clap::{Parser, Subcommand};#[derive(Parser)]
#[clap(name = "todo")]
#[clap(about = "一个简单的待办事项管理器", long_about = None)]
pub struct Cli {#[clap(subcommand)]pub command: Commands,/// 任务文件的路径#[clap(short, long, default_value = "~/.todo.json")]pub file: String,
}#[derive(Subcommand)]
pub enum Commands {/// 添加一个新任务Add {/// 任务描述#[clap(required = true)]description: Vec<String>,},/// 列出所有任务List {/// 是否显示已完成的任务#[clap(short, long)]all: bool,},/// 将任务标记为已完成Complete {/// 任务 ID#[clap(required = true)]id: usize,},/// 删除任务Delete {/// 任务 ID#[clap(required = true)]id: usize,},
}

实现主程序

最后,我们需要实现主程序,将所有组件连接起来。更新 src/main.rs 文件:

mod cli;
mod storage;
mod task;use anyhow::{Context, Result};
use clap::Parser;
use cli::{Cli, Commands};
use colored::*;
use storage::Storage;
use std::path::PathBuf;fn main() -> Result<()> {let cli = Cli::parse();// 处理 ~ 路径let file_path = if cli.file.starts_with("~") {let home = dirs::home_dir().context("无法确定主目录")?;let path = cli.file.strip_prefix("~").unwrap();let mut path_buf = PathBuf::from(home);path_buf.push(path.trim_start_matches('/'));path_buf.to_string_lossy().to_string()} else {cli.file};let mut storage = Storage::new(&file_path)?;match cli.command {Commands::Add { description } => {let desc = description.join(" ");let task = storage.add_task(desc)?;println!("{} {}", "已添加任务:".green(), task);}Commands::List { all } => {let tasks = storage.list_tasks(all);if tasks.is_empty() {println!("{}", "没有任务".yellow());return Ok(());}println!("{}", "任务列表:".green());for task in tasks {println!("{}", task);}}Commands::Complete { id } => {storage.complete_task(id)?;println!("{} {}", "已完成任务 ID:".green(), id);}Commands::Delete { id } => {let task = storage.delete_task(id)?;println!("{} {}", "已删除任务:".green(), task);}}Ok(())
}

别忘了添加 dirs 依赖项到 Cargo.toml

[dependencies]
dirs = "4.0"

构建和运行

现在,我们可以构建和运行我们的待办事项管理器:

cargo build --release

使用示例:

# 添加任务
./target/release/todo-cli add 完成 Rust 项目实践章节# 列出任务
./target/release/todo-cli list# 列出所有任务(包括已完成)
./target/release/todo-cli list --all# 完成任务
./target/release/todo-cli complete 1# 删除任务
./target/release/todo-cli delete 1

项目扩展

现在我们有了一个基本的待办事项管理器,但还有很多方面可以改进和扩展:

1. 添加优先级

我们可以为任务添加优先级字段,并允许用户按优先级排序:

// 在 Task 结构体中添加
pub enum Priority {Low,Medium,High,
}// 添加排序功能
pub fn list_tasks_by_priority(&self) -> Vec<&Task> {let mut tasks = self.list_tasks(false);tasks.sort_by(|a, b| b.priority.cmp(&a.priority));tasks
}

2. 添加截止日期

我们可以为任务添加截止日期,并提供查看即将到期任务的功能:

// 在 Task 结构体中添加
pub due_date: Option<DateTime<Local>>,// 添加查看即将到期任务的功能
pub fn list_upcoming_tasks(&self, days: i64) -> Vec<&Task> {let now = Local::now();let deadline = now + chrono::Duration::days(days);self.tasks.iter().filter(|task| {if let Some(due_date) = task.due_date {!task.completed && due_date <= deadline} else {false}}).collect()
}

3. 添加标签

我们可以为任务添加标签,并允许用户按标签过滤:

// 在 Task 结构体中添加
pub tags: Vec<String>,// 添加按标签过滤的功能
pub fn list_tasks_by_tag(&self, tag: &str) -> Vec<&Task> {self.tasks.iter().filter(|task| task.tags.contains(&tag.to_string())).collect()
}

4. 添加数据库支持

我们可以使用 SQLite 或其他数据库替代 JSON 文件存储,以提高性能和可靠性:

// 使用 rusqlite 库
use rusqlite::{params, Connection, Result};pub struct DbStorage {conn: Connection,
}impl DbStorage {pub fn new(db_path: &str) -> Result<Self> {let conn = Connection::open(db_path)?;conn.execute("CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY KEY,description TEXT NOT NULL,completed BOOLEAN NOT NULL DEFAULT 0,created_at TEXT NOT NULL)",[],)?;Ok(Self { conn })}// 实现其他方法...
}

5. 添加 Web 界面

我们可以使用 Rocket 或 Actix Web 框架添加一个 Web 界面,使用户可以通过浏览器管理任务:

// 使用 Rocket 框架
#[macro_use] extern crate rocket;#[get("/tasks")]
fn list_tasks() -> Json<Vec<Task>> {let storage = Storage::new("~/.todo.json").unwrap();Json(storage.list_tasks(true).into_iter().cloned().collect())
}#[post("/tasks", data = "<task>")]
fn add_task(task: Json<NewTask>) -> Json<Task> {let mut storage = Storage::new("~/.todo.json").unwrap();let new_task = storage.add_task(task.description.clone()).unwrap();Json(new_task.clone())
}#[launch]
fn rocket() -> _ {rocket::build().mount("/api", routes![list_tasks, add_task])
}

最佳实践

在开发这个项目的过程中,我们应用了许多 Rust 最佳实践:

1. 错误处理

我们使用 anyhow 库进行错误处理,提供了详细的错误上下文信息,使调试更容易。

2. 模块化设计

我们将代码分为多个模块(task.rsstorage.rscli.rs),每个模块负责特定的功能,使代码更易于维护。

3. 使用适当的类型

我们使用了适当的类型来表示数据,如 DateTime<Local> 表示时间,usize 表示 ID,这有助于防止类型错误。

4. 文档和注释

我们为代码添加了文档注释,使其他开发者更容易理解代码的功能和用法。

5. 测试

虽然我们没有展示测试代码,但在实际项目中,应该为每个模块编写单元测试和集成测试,确保代码的正确性。

练习题

  1. 实现上述扩展功能之一(优先级、截止日期或标签)。

  2. 添加一个新命令 edit,允许用户编辑现有任务的描述。

  3. 实现任务的导入和导出功能,支持 CSV 格式。

  4. 添加一个统计功能,显示已完成和未完成任务的数量,以及完成率。

  5. 将存储逻辑改为使用 SQLite 数据库,而不是 JSON 文件。

总结

在本章中,我们构建了一个完整的命令行待办事项管理器,应用了前面章节学到的 Rust 知识:

  • 使用结构体和枚举表示数据
  • 实现特质(如 Display)自定义行为
  • 使用 serde 进行序列化和反序列化
  • 使用 clap 解析命令行参数
  • 使用 anyhow 进行错误处理
  • 组织代码为多个模块

通过这个项目,你应该对如何在实际应用中使用 Rust 有了更深入的理解。记住,最好的学习方法是实践,所以尝试完成练习题,或者扩展这个项目,添加你自己想要的功能。

恭喜你完成了 Rust 进阶篇的学习!现在你已经掌握了 Rust 的核心概念和实践技能,可以开始构建自己的 Rust 项目了。继续探索 Rust 生态系统,参与开源项目,不断提升你的 Rust 编程能力!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词