从迷你todo 命令行入门Rust示例详解

目录
  • 一个迷你 todo 应用
  • 需要安装的依赖
  • 文件目录组织
  • 主文件
  • 读取文件
  • 状态处理工厂函数
  • Trait(特征)
    • Create trait
    • Get trait
    • Delete trait
    • Edit trait
    • 导出 trait
  • 为 struct 实现 trait
    • Pending
    • Done
    • 导出 struct
  • Process 输入处理
  • 最后

一个迷你 todo 应用

该文章将使用 Rust 从零去做一个入门级别的 TODO 命令行应用

你将学会什么?

  • 基本的命令行操作
  • 文件读写和文件结构组织

我们将会通过命令行输入来实现对根目录下 state.json 文件的编辑,如:

cargo run create 买菜

cargo run get 买菜

cargo run delete 买菜

cargo run edit 买菜

我们的 todo 状态会有 pending 和 done 这两种,create 操作将创建一个 {"买菜":"pending"} 的状态,edit 操作主要就是将 pending 状态转变为 done 状态

需要安装的依赖

我们要操作 json 数据结构,所以要安装下面这个 crate

[package]
edition = "2021"
name = "todo_app"
version = "0.1.0"
[dependencies]
serde_json = {default-feature = false, version = "1.0", feature = ["alloc"]}

文件目录组织

|   main.rs               主文件
|   process.rs            处理 todo 命令行输入
|   state.rs              读写文件
|
\---todo
    |   mod.rs            类似于 js 里面的 index.js
    |
    \---structs           struct 数据结构组织
        |   base.rs       基础参数的数据结构
        |   done.rs       完成状态的数据结构
        |   mod.rs        类似于 js 里面的 index.js
        |   pending.rs    处理中状态的数据结构
        |
        \---traits        (特征)类似于 ts 里面的 interface
                create.rs 创建操作
                delete.rs 删除操作
                edit.rs   编辑操作
                get.rs    查询操作
                mod.rs    类似于 js 里面的 index.js

主文件

// 有一点点像 import process from './process.rs'
mod process;
mod state;
mod todo;
// 使用外部的库、标准库或自己定义的工具
use process::process_input;
use serde_json::Map;
use serde_json::Value;
use state::read_file;
use std::env;
use todo::todo_factory;
fn main() {
    // 收集所有命令行的参数,转换成数组
    let args: Vec<String> = env::args().collect();
    // 拿到第一个参数如 get、delete、edit、create
    let command: &String = &args[1];
    // 第二个参数是用来记录事件的
    let title: &String = &args[2];
    // 读取根目录的 state.json 文件并转换成 map json 结构
    let state: Map<String, Value> = read_file("./state.json");
    // 如果事件已经存取过了,那就直接获取,没有就将其状态设置为 pending
    let status: String;
    match &state.get(title) {
        Some(result) => status = result.to_string().replace('\"', ""),
        None => status = "pending".to_string(),
    }
    // todo_factory 工厂函数用于处理 pending 和 done 状态的输入
    let item = todo_factory(&status, title).expect(&status);
    // 将状态输入到本地文件中
    process_input(item, command.to_string(), &state);
}

上面我们看到了 3 个主要我们需要自己编写的函数:

  • read_file 读取文件
  • todo_factory 状态工厂函数
  • process_input 处理输入

接下来让我们一个一个来看下这几个函数

读取文件

read_file 函数位于 src/state.rs 路径下,该文件主要是用来存储状态操作的,里面包含读取和写入两个函数,让我们主要看下 read_file 这个函数,它的功能:

  • 打开文件
  • 读取文件
  • 将读取到的文件内容转成 json 对象
  • 返回读取到的 json 对象
use std::fs::{self, File};
use std::io::Read;
use serde_json::json;
use serde_json::value::Value;
use serde_json::Map;
/** 读取文件 */
pub fn read_file(file_name: &str) -> Map<String, Value> {
    // 打开文件
    let mut file = File::open(file_name).unwrap();
    // 创建一个 string buffer 用于存储数据
    let mut data = String::new();
    // 读取数据写入到 buffer
    file.read_to_string(&mut data).unwrap();
    // 将读取到的字符串转换成 json 文本格式 Value 类型
    let json: Value = serde_json::from_str(&data).unwrap();
    // 将 json 文本格式转换成 json 对象
    let state: Map<String, Value> = json.as_object().unwrap().clone();
    // 将这个对象返回出去
    return state;
}
/** 写入文件 */
pub fn write_to_file(file_name: &str, state: &mut Map<String, Value>) {
    // json! 这个宏用于将 json 字面量对象转换回 json 文本 Value 格式
    let new_data = json!(state);
    // 将 json 文本写入到文件中
    fs::write(file_name, new_data.to_string()).expect("unable to write to file");
}

状态处理工厂函数

看完文件读取操作我们再来看下第二个主要函数 todo_factory,这个函数主要是根据事件的状态和通过命令行输入的 title 事件名称,然后构建出一个相应的数据结构

该文件位于 src/todo/mod.rs 路径下

它的作用主要是根据输入的 pending/done 状态,然后创建出一个对应的数据结构

// 将 structs 向外部导出
pub mod structs;
use structs::done::Done;
use structs::pending::Pending;
// 创建一个包含 pending 和 done 状态的枚举
pub enum ItemTypes {
    Pending(Pending),
    Done(Done),
}
/** 状态处理工厂函数 */
pub fn todo_factory(item_type: &str, item_title: &str) -> Result<ItemTypes, &'static str> {
    match item_type {
        "pending" => {
            // 创建一个 pending 状态的数据结构,然后返回出去
            let pending_item = Pending::new(item_title);
            Ok(ItemTypes::Pending(pending_item))
        }
        "done" => {
            // 创建一个 done 状态的数据结构,然后返回出去
            let done_item = Done::new(item_title);
            Ok(ItemTypes::Done(done_item))
        }
        // 如果不是这两个状态就返回一个错误
        _ => Err("This is not accepted!"),
    }
}

从上面的代码中我们可以看到下面这两行代码,这是我们主要需要定义的两个状态数据结构,他们位于 src/todo/structs 路径下

use structs::done::Done;
use structs::pending::Pending;

不过在看上面两个数据结构之前,我们需要先来看下另一个基础的数据结构,就是 base,因为上面两个数据结构是基于 base 的,实现一个类似于面向对象语言里继承的做法,base 是它们俩的基类

该文件位于 src/todo/structs/base.rs 路径下

pub struct Base {
    pub title: String,
    pub status: String,
}
// 为这个 Base 数据结构实现一个 new 方法,并返回一个实例化后的数据结构
impl Base {
    pub fn new(input_title: &str, input_status: &str) -> Base {
        Base {
            title: input_title.to_string(),
            status: input_status.to_string(),
        }
    }
}

现在我们用 Pending 和 Done 两个 struct 来 "继承" Base

该文件位于 src/todo/structs/pending.rs 路径下

use super::base::Base;
pub struct Pending {
    pub super_struct: Base,
}
impl Pending {
    pub fn new(input_title: &str) -> Pending {
        Pending {
            super_struct: Base::new(input_title, "pending"),
        }
    }
}

该文件位于 src/todo/structs/done.rs 路径下

use super::base::Base;
pub struct Done {
    pub super_struct: Base,
}
impl Done {
    pub fn new(input_title: &str) -> Done {
        Done {
            super_struct: Base::new(input_title, "done"),
        }
    }
}

Trait(特征)

Trait 类似于 TS 里的 interface 接口,现在我们要为我们的 struct 来实现一些在 Trait 里面定义的方法,文件路径在 src/todo/structs/traits

Create trait

文件路径在 src/todo/structs/traits/create

use serde_json::{json, Map, Value};
use crate::state::write_to_file;
pub trait Create {
    // 为这个 trait 实现一个默认的 create 方法
    fn create(&self, title: &String, status: &String, state: &mut Map<String, Value>) {
        state.insert(title.to_string(), json!(status));
        write_to_file("./state.json", state);
        println!("\n\n{} is being created\n\n", title);
    }
}

Get trait

文件路径在 src/todo/structs/traits/get

use serde_json::{Map, Value};
pub trait Get {
    fn get(&self, title: &String, state: &Map<String, Value>) {
        let item = state.get(title);
        match item {
            Some(result) => {
                println!("\n\nItem: {}", title);
                println!("Status: {} \n\n", result);
            }
            None => println!("item: {} was not find", title),
        }
    }
}

Delete trait

文件路径在 src/todo/structs/traits/delete

use serde_json::{Map, Value};
use crate::state::write_to_file;
pub trait Delete {
    fn delete(&self, title: &String, state: &mut Map<String, Value>) {
        state.remove(title);
        write_to_file("./state.json", state);
        println!("\n\n{} is being deleted\n\n", title);
    }
}

Edit trait

该 Trait 主要实现两个方法,一个是将事件设置为 pending 状态,一个将事件设置为 done 状态

文件路径在 src/todo/structs/traits/edit

use serde_json::{json, Map, Value};
use crate::state::write_to_file;
pub trait Edit {
    fn set_to_done(&self, title: &String, state: &mut Map<String, Value>) {
        state.insert(title.to_string(), json!("done".to_string()));
        write_to_file("./state.json", state);
        println!("\n\n{} is being set to done\n\n", title);
    }
    fn set_to_pending(&self, title: &String, state: &mut Map<String, Value>) {
        state.insert(title.to_string(), json!("pending".to_string()));
        write_to_file("./state.json", state);
        println!("\n\n{} is being set to pending\n\n", title);
    }
}

导出 trait

文件路径在 src/todo/structs/traits/mod

pub mod create;
pub mod delete;
pub mod edit;
pub mod get;

为 struct 实现 trait

接下来我们为 Pending 和 Done 两个 struct 来实现这几个 trait

Pending

我们为 Pending 实现所有的 4 个 trait

use super::base::Base;
use super::traits::create::Create;
use super::traits::delete::Delete;
use super::traits::edit::Edit;
use super::traits::get::Get;
pub struct Pending {
    pub super_struct: Base,
}
impl Pending {
    pub fn new(input_title: &str) -> Pending {
        Pending {
            super_struct: Base::new(input_title, "pending"),
        }
    }
}
impl Delete for Pending {}
impl Create for Pending {}
impl Edit for Pending {}
impl Get for Pending {}

Done

为 Done 实现除了 Create 以外的 trait

文件路径在 src/todo/structs/done

use super::base::Base;
use super::traits::delete::Delete;
use super::traits::edit::Edit;
use super::traits::get::Get;
pub struct Done {
    pub super_struct: Base,
}
impl Done {
    pub fn new(input_title: &str) -> Done {
        Done {
            super_struct: Base::new(input_title, "done"),
        }
    }
}
impl Delete for Done {}
impl Edit for Done {}
impl Get for Done {}

导出 struct

mod base;
pub mod done;
pub mod pending;
pub mod traits;

Process 输入处理

文件位于 src/process.rs 路径下

use serde_json::{Map, Value};
use crate::todo::{
    structs::{
        done::Done,
        pending::Pending,
        traits::{create::Create, delete::Delete, edit::Edit, get::Get},
    },
    ItemTypes,
};
// 处理 pending 状态
fn process_pending(item: Pending, command: String, state: &Map<String, Value>) {
    let mut state = state.clone();
    // 根据用户的输入来调用不同的方法
    match command.as_str() {
        "get" => item.get(&item.super_struct.title, &state),
        "create" => item.create(
            &item.super_struct.title,
            &item.super_struct.status,
            &mut state,
        ),
        "delete" => item.delete(&item.super_struct.title, &mut state),
        "edit" => item.set_to_done(&item.super_struct.title, &mut state),
        _ => println!("command: {} is not supported", command),
    }
}
// 处理 done 状态
fn process_done(item: Done, command: String, state: &Map<String, Value>) {
    let mut state = state.clone();
    match command.as_str() {
        "get" => item.get(&item.super_struct.title, &state),
        "delete" => item.delete(&item.super_struct.title, &mut state),
        "edit" => item.set_to_pending(&item.super_struct.title, &mut state),
        _ => println!("command: {} is not supported", command),
    }
}
// 处理用户的输入,根据输入来匹配枚举,然后执行不同的操作
pub fn process_input(item: ItemTypes, command: String, state: &Map<String, Value>) {
    match item {
        ItemTypes::Pending(item) => process_pending(item, command, state),
        ItemTypes::Done(item) => process_done(item, command, state),
    }
}

最后

按照上面的代码一步一步来完成就可以执行程序了,在根目录下新建一个 state.json 文件,写入一个空对象,不然会报错(代码没做处理)

{}

最后再在控制台上去执行

cargo run create shopping

然后就能看到 state.json 中多了一条记录

{ "shopping": "pending" }

其它的方法就交由你们自己去尝试好了~

以上就是从迷你 todo 命令行入门 Rust的详细内容,更多关于todo 命令行入门Rust的资料请关注我们其它相关文章!

(0)

相关推荐

  • Rust Atomics and Locks并发基础理解

    目录 Rust 中的线程 线程作用域 所有权共享 借用和数据竞争 内部可变 rust 中的线程安全 Send 和 Sync 线程阻塞和唤醒 Rust 中的线程 在 Rust 中,线程是轻量级的执行单元,可以并行执行多个任务.Rust 中的线程由标准库提供的 std::thread 模块支持,使用线程需要在程序中引入该模块.可以使用 std::thread::spawn() 函数创建一个新线程,该函数需要传递一个闭包作为线程的执行体.闭包中的代码将在新线程中执行,从而实现了并发执行.例如: use

  • Rust Atomics and Locks 源码解读

    目录 正文 load 和 store 使用 AtomicBool实现通知线程停止的案例 正文 在 Rust 中,原子性操作是指在多线程并发环境下对共享数据进行操作时,保证操作的原子性,即不会出现数据竞争等问题.Rust 提供了原子类型和原子操作来支持多线程并发编程. Rust 的原子类型包括 AtomicBool.AtomicIsize.AtomicUsize.AtomicPtr 等.这些类型的实现都使用了底层的原子操作指令,保证了它们的读写操作是原子的,不会被其他线程中断. 在 Rust 中,

  • 向Rust学习Go考虑简单字符串插值特性示例解析

    目录 fmt.Printf 或 fmt.Sprintf 写拼装字符串业务 简单字符串插值 其他语言例子 Swift Kotlin C Rust 争论矛盾点 总结 fmt.Printf 或 fmt.Sprintf 写拼装字符串业务 在日常开发 Go 工程中,我们经常会用 fmt.Printf 或 fmt.Sprintf 去写类似的拼装字符串的业务. 如下代码: fmt.Printf("Hello Gopher %s, you are %d years old and you're favorite

  • Rust Atomics and Locks内存序Memory Ordering详解

    目录 Rust内存序 重排序和优化 happens-before Relexed Ordering Release 和 Acquire Ordering SeqCst Ordering Rust内存序 Memory Ordering规定了多线程环境下对共享内存进行操作时的可见性和顺序性,防止了不正确的重排序(Reordering). 重排序和优化 重排序是指编译器或CPU在不改变程序语义的前提下,改变指令的执行顺序.在单线程环境下,重排序可能会带来性能提升,但在多线程环境下,重排序可能会破坏程序

  • Rust 所有权机制原理深入剖析

    目录 what's ownership? Scope (作用域) ownership transfer(所有权转移) move clone copy References and Borrowing(引用和借用) Mutable References(可变引用) Dangling References(悬垂引用) what's ownership? 常见的高级语言都有自己的 Garbage Collection(GC)机制来管理程序运行的内存,例如 Java.Go 等.而 Rust 引入了一种全

  • 从迷你todo 命令行入门Rust示例详解

    目录 一个迷你 todo 应用 需要安装的依赖 文件目录组织 主文件 读取文件 状态处理工厂函数 Trait(特征) Create trait Get trait Delete trait Edit trait 导出 trait 为 struct 实现 trait Pending Done 导出 struct Process 输入处理 最后 一个迷你 todo 应用 该文章将使用 Rust 从零去做一个入门级别的 TODO 命令行应用 你将学会什么? 基本的命令行操作 文件读写和文件结构组织 我

  • Python命令行解析器argparse详解

    目录 第1章 argparse简介 1.1 解析 1.2 argparse定义三步骤 1.3  代码示例 第2章 参数详解 2.1 创建一个命令行解析器对象:ArgumentParser() 2.2 为命令行添加参数: add_argument() 方法 2.3 解析命令行的参数:parse_args() 2.4 命令行参数的输入 2.5 命令行参数的使用 总结 第1章 argparse简介 1.1 解析 argparse 模块是 Python 内置的一个用于命令项选项与参数解析的模块,argp

  • MySQL 设计和命令行模式下建立详解

    MySQL 设计和命令行模式下建立详解 系列文章: MySQL 设计和命令行模式下建立详解 C++利用MySQL API连接和操作数据库实例详解 1.数据表的设计 MySQL数据库管理系统(DBMS)中,包含的MySQL中定义数据字段的类型对你数据库的优化是非常重要的.MySQL支持多种类型,大致可以分为三类:数值.日期/时间和字符串(字符)类型. 下面以大学熟悉的学生选课管理系统中用到的数据库为例,来设计相应的数据表.主要有三张表:学生表,课程表和选课表. 学生表设计: 字段(Field) 类

  • Linux终端命令行的常用快捷键详解

    history 显示命令历史列表 ↑(Ctrl+p) 显示上一条命令 ↓(Ctrl+n) 显示下一条命令 !num 执行命令历史列表的第num条命令 !! 执行上一条命令 !?string? 执行含有string字符串的最新命令 Ctrl+r 然后输入若干字符,开始向上搜索包含该字符的命令,继续按Ctrl+r,搜索上一条匹配的命令 Ctrl+s 与Ctrl+r类似,只是正向检索 Alt+< 历史列表第一项 Alt+> 历史列表最后一项 Ctrl+f 光标向前移动一个字符,相当与-> Ct

  • php根据命令行参数生成配置文件详解

    像npm, composer等工具,在开始使用的使用,都需要初始化项目,生成一个项目的配置文件.这种功能的原理是怎么实现的呢? 比如: D:\>npm init --yes Wrote to D:\package.json: { "name": "", "version": "1.0.0", "description": "", "main": "in

  • php命令行写shell实例详解

    php 可以像java perl python 那样运行,今天发现如果我早早知道这个,或许我不会去学习java 和 python 当年学java不过为了一个程序放在服务器上,不停的跑啊跑,原来 php 也可以. php -h Usage: php [options] [-f] <file> [--] [args...] php [options] -r <code> [--] [args...] php [options] [-B <begin_code>] -R &l

  • springboot命令行启动的方法详解

    springboot命令行启动 创建的springboot项目想看看效果,不想打开idea等开发工具,使用直接使用命令行启动. maven的命令启动 需要将 jdk的bin目录和maven的bin目录添加到环境变量path中,若是没有,mvn就要用在maven的bin环境中的全路径 若是没有添加环境变量 mvn就要是E:\software\apache-maven-3.3.9\bin\mvn(安装路径\bin\mvn) java就要是C:\software\jdk\bin\java.exe(安装

  • php命令行模式代码实例详解

    php全集行模式,即php-cli,官方文档中称为: CLI SAPI(Server Application Programming Interface,服务端应用编程端口).听着挺复杂.其实是因为php原本为服务器端的脚本语言,所以引申出这个叫法. 与服务端模式的不同 服务端模式主要有两种工作方式: 作为web server的模式方式或作为一个cgi可执行程序. 前者,比如作为apach中的一个模块(如:php5apache2.dll); 后者作为可执行程序,如php-cig. 现在的替代者为

  • C语言命令行参数的使用详解

    之前曾经使用过很多次c语言的命令行参数了,但是总是每次使用的时候都不太确定,需要重新查资料,这次来个总结.c语言的命令行参数非常简单,只需要一个简单的例子就可以说明: #include <stdio.h> void main(int argc,char** argv) { printf("%d\n",argc); printf("%s\n",argv[0]); printf("%s\n",argv[1]); printf("

  • Linux Shell脚本多命令执行逻辑的示例详解

    目录 简介 一.分号 二.&& 三.|| 案例剖析 简介 Linux 中可以使用分号";“.双and号”&&“和双竖线”||"来连接多个命令.根据场景的不同适当的使用合适的符号. 历史攻略 python:执行dos命令.Linux命令 命令连接符解析: 仅连接,表示运行先后,无逻辑关系:分号";",如command1 ; command2 逻辑与关系:&&,如command1 && command2 逻

随机推荐