【Rust探索之旅】构建安全、高效的本地待办事项工具 TodoLite(系列一)

Rust探索之旅・开发者技术创作征文活动 10w+人浏览 658人参与

Rust号称目前最快的编程语言,实际上很早就想写一个完整的,且系列的技术实践博文了,正好参与官网的写作活动,那么就慢慢来吧。

请添加图片描述

TodoLite 系列 (一):项目奠基,用 Serde 定义我们的数据结构

1. 为什么选择 Rust 构建 TodoLite

在开始敲下第一行代码前,让我们先思考一个问题:市面上的待办事项工具琳琅满目,我们为什么还要自己造轮子,而且偏偏选择 Rust
在线工具的困境:像 Todoist 这样的云端服务功能强大,但我们的任务数据存储在第三方服务器上,始终存在隐私泄露的风险,并且依赖网络连接。

本地笔记的局限:使用记事本或 Notepad++ 虽然简单直接,数据也在本地,但它们缺乏结构化管理和核心的提醒功能,很容易让我们忘记重要的任务。

TodoLite 的愿景正是为了解决这些痛点:它是一款纯本地的应用,数据由你掌控;它安全可靠,所有数据都经过强加密;它智能高效,能在任务到期时通过系统通知提醒你。
而 Rust 则是实现这一愿景的完美选择:

  • 性能与效率:Rust 拥有媲美 C/C++ 的性能,没有 GC(垃圾回收)带来的延迟,能确保我们的应用即使在处理大量任务时也流畅响应。
  • 无与伦比的安全性:Rust 的所有权系统和借用检查器能在编译期就消除一整类的内存安全漏洞(如空指针、数据竞争),这对于开发一个需要处理敏感数据和加密逻辑的应用来说至关重要。
  • 强大的生态系统:Rust 的包管理器 Cargo 和社区贡献的库 (Crates) 质量极高,无论是数据序列化 (Serde)、加密 (Ring) 还是 GUI (Slint),都有成熟、现代的解决方案。

废话不多说了,让我们正式开启 TodoLite 的构建之旅吧!

2. 环境搭建与项目创建

首先,请确保你已经安装了 Rust 工具链。如果没有,可以通过官网 rust-lang.org 的指引轻松安装。
打开你的终端,执行以下命令来创建一个新的 Rust 项目:

cargo new todolite
cd todolite

Cargo 会为我们生成一个标准项目结构。现在,我们来规划一下 src 目录下的代码模块,这有助于保持代码的整洁和高内聚:

src/
├── main.rs      # 应用主入口和 CLI/GUI 启动逻辑
├── model.rs     # 定义核心数据结构,如 Task
├── storage.rs   # 负责数据的加载、保存和加密
└── cli.rs       # (未来) 存放命令行界面的逻辑

暂时不需要创建所有文件,随着开发的进行再逐一添加。

3. 核心数据建模,用 structenum 描绘任务

万丈高楼平地起,一个应用的核心在于其数据模型。我们需要定义一个结构体来表示单条待办事项。
首先,我们需要添加几个依赖库到 Cargo.toml 文件中:

  • serde:这是 Rust 生态中进行序列化和反序列化的事实标准。
  • serde_json:serde 的一个实现,用于处理 JSON 格式。
  • chrono:用于处理日期和时间。
    打开 Cargo.toml 文件,在 [dependencies] 部分添加如下内容:
Toml
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }

注意:我们为 serde 开启了 derive 特性,这将允许我们使用宏自动实现序列化;为 chrono 开启了 serde 特性,使其能够与 serde 无缝集成。
现在,在 src/ 目录下创建 model.rs 文件,并写入以下代码:

// src/model.rs

use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc};

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Task {
    pub id: u64,
    pub content: String,
    pub creation_date: DateTime<Utc>,
    pub due_date: Option<DateTime<Utc>>, // 截止日期是可选的
    pub is_completed: bool,
}

让我们来解析一下这段代码:

  • 我们定义了一个 Task 结构体,包含了 id、内容、创建日期、可选的截止日期和完成状态。
  • #[derive(Serialize, Deserialize, Debug, Clone)] 是这段代码的魔法所在。这是一个“派生宏”:
    • Serialize 和 Deserialize 来自 serde,它们会在编译时自动为 Task 结构体生成序列化(将 Rust 结构体转换为 JSON 等格式)和反序列化(从 JSON 等格式恢复为 Rust 结构体)的代码。
    • Debug 允许我们使用 println!(“{:?}”, task) 来打印结构体,方便调试。
    • Clone 允许我们创建 Task 的一个完整副本。

深入 Serde 宏的工作原理

当编译器看到 #[derive(Serialize)] 时,serde 的宏处理器会介入。它会分析 Task 结构体的每一个字段(如 id, content),然后自动生成一个 impl serde::Serialize for Task 的代码块。这个代码块会精确地描述如何将一个 Task 实例的每个字段写入到一个通用的数据格式中。反之,Deserialize 宏则生成如何从通用数据格式中读取数据并填充 Task 实例的代码。
这正是 Rust “零成本抽象”的体现。

4. 首次持久化尝试,让数据落地

模型已经定义好了,现在让我们来验证一下它能否被正确地保存到文件中。打开 src/main.rs,我们将在这里编写一个简单的测试程序。
首先,在 main.rs 的开头声明并引入我们的 model 模块:

// src/main.rs
mod model;
use model::Task;

use chrono::Utc;

然后,修改 main 函数,实现创建任务、序列化并写入文件的完整流程

// src/main.rs
// ... (之前的 use 语句)

fn main() {
    // 1. 创建一些示例任务
    let tasks = vec![
        Task {
            id: 1,
            content: "学习 Rust 的 Serde 库".to_string(),
            creation_date: Utc::now(),
            due_date: None,
            is_completed: false,
        },
        Task {
            id: 2,
            content: "完成 TodoLite 系列第一篇文章".to_string(),
            creation_date: Utc::now(),
            due_date: Some(Utc::now() + chrono::Duration::days(1)),
            is_completed: false,
        },
    ];


    // 2. 将任务列表序列化为 JSON 字符串
    //    使用 to_string_pretty 可以得到格式化的 JSON,便于阅读
    let json_data = serde_json::to_string_pretty(&tasks).expect("Failed to serialize tasks.");
    println!("序列化后的 JSON 数据:\n{}", json_data);

    // 3. 将 JSON 字符串写入本地文件
    let file_path = "tasks.json";
    std::fs::write(file_path, json_data).expect("Failed to write to tasks.json.");
    println!("\n任务已成功保存到 {}", file_path);

    // (可选) 验证:从文件中读回并反序列化
    let read_json_data = std::fs::read_to_string(file_path).expect("Failed to read from tasks.json.");
    let loaded_tasks: Vec<Task> = serde_json::from_str(&read_json_data).expect("Failed to deserialize tasks.");
    println!("\n从文件加载的任务: \n{:#?}", loaded_tasks);
}

现在,回到终端,运行你的程序:

cargo run

如果一切顺利,你将看到控制台打印出序列化后的 JSON 数据,并提示已保存。更重要的是,项目根目录下会多出一个 tasks.json 文件,内容如下:

[
  {
    "id": 1,
    "content": "学习 Rust 的 Serde 库",
    "creation_date": "...",
    "due_date": null,
    "is_completed": false
  },
  {
    "id": 2,
    "content": "完成 TodoLite 系列第一篇文章",
    "creation_date": "...",
    "due_date": "...",
    "is_completed": false
  }
]

我们成功地完成了数据从内存中的结构体到硬盘上的文件的部分!

总结

本篇,我们为 TodoLite 项目搭建了骨架,使用 serdechrono 定义了健壮的核心数据模型,并验证了其本地持久化的能力。
然而,我们目前的实现还很脆弱。std::fs::write 是一条捷径,它背后的错误处理机制被 .expect() 粗暴地掩盖了。如果文件写入失败(例如磁盘已满、没有权限),程序就会直接崩溃。
在下一篇文章中,我们将彻底改造这一部分,引入专业的 I/O 操作和 Rust 优雅的错误处理机制,为 TodoLite 构建一个真正可靠的数据存储层。各位小伙伴,敬请期待!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

断水流大撕兄

你的鼓励,就是我最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值