Rust 项目实战:从头构建一个笔记命令行程序(1)

polarisxu

共 6446字,需浏览 13分钟

 ·

2022-02-10 22:28

学习了 Rust 的语法后,我对这门语言感觉良好。于是计划实战一下。这篇文章是系列文章中的第一篇,该系列将介绍如何使用 Rust 构建笔记应用程序。

最终目标是为读者提供足够的知识来构建自己的类似于 engram[1] 的笔记应用程序。

本系列内容希望读者具备一些编程知识,但可能是 Rust 新手。该系列的每个部分都将产生一个功能性和有用的应用程序,将在以后的文章中构建。

我强烈建议手动敲入你看到的代码,而不仅仅是复制和粘贴。这是获得更好理解的最有效方法之一。

01 入门

从 Rust 官网找到入门页面:https://www.rust-lang.org/learn/get-started,根据介绍安装 Rust。

安装 Rust

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

创建新的 Rust 项目

cargo new notes

cargo 是 Rust 包管理器[2]。它支持多种命令,本文将介绍其中的几个。cargo new 在当前目录中创建一个名为 notes(或你指定的任何其他内容)的新项目(和文件夹)。

src/main.rs 包含一个简单的“Hello World”应用程序。

Cargo.toml 被称为清单。请参阅 The Cargo Book[3] 以深入了解此处支持的内容。我们只需要在这里做一个小的调整来添加一个依赖项,所以不要太担心现在不理解全部内容。

默认情况下,.gitignore 可以方便地从 git 中忽略目标文件夹。

运行 hello world 应用程序

cargo run

cargo run[4] 命令构建并运行当前的包。在本教程进行更改后,你将使用它来运行和测试这些更改。运行一次后,你会看到更多的文件和文件夹出现。

Cargo.lock 这是一个自动生成的文件,它准确指定了正在使用的库的版本。有关 Cargo.toml 与 Cargo.lock 的更多详细信息,请参阅 Cargo Book[5]

target 这个文件夹是存储所有构建文件的地方。你几乎可以忽略这一点,因为 cargo 工具会根据需要处理它。

在 VS Code 中打开的默认 Rust 工作区

02 准备数据模型

先从数据库开始。

创建一个 sqlite3 数据库

数据库是几乎所有应用程序的核心。大多数新功能需要存储一些新数据或以某种方式检索现有信息。由于这些原因,这通常是你在构建新事物时应该考虑的第一件事。特别是,我试图构建的 “schema” 是什么。

就本教程而言,我们的 schema 非常简单。我们想要一个带有 id 列和 body 列的 notes 表。id 存储特定笔记的唯一标识符。这是大多数数据库的必填字段,因为它允许你直接引用现有项目。body 列将存储我们正在保存的笔记的内容。欢迎你在这里选择其他一些你觉得更好的术语。一些可能的选择:内容、消息、文本或标题。事后当然可以改变这一点,但随着时间的推移,越来越多的代码引用这些特定术语,改变会越来越困难,所以试着选择一个你能接受的并坚持下去。

我现在看到的大多数教程主要关注在服务器上存储数据并通过某种 API 同步它。这个系列最终会那样实现,但像我们正在构建的笔记应用程序离线工作是非常重要的。尽早设置此限制允许我们考虑离线构建——而不是试图将其绑定到现有的云应用程序。

sqlite3[6] 是一个流行的数据库库,它将数据库存储在文件系统上的单个文件中。这对用户来说很简单,因为他们不需要运行单独的数据库服务器,并且如果需要,数据库文件可以传递到其他系统。

创建笔记表

第一步是创建一个表来存放我们的应用程序数据。我们将使用 rusqlite 库来处理我们与 sqlite 数据库的连接。你可以通过修改 Cargo.toml 来安装它。

Cargo.toml

[package]
name = "notes"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at [https://doc.rust-lang.org/cargo/reference/manifest.html](https://doc.rust-lang.org/cargo/reference/manifest.html "https://doc.rust-lang.org/cargo/reference/manifest.html")

[dependencies.rusqlite]
version = "0.26.1"
features = ["bundled"]

这会添加 rusqlite 作为依赖项,下次尝试构建时将安装该依赖项。features = ["bundled"] 告诉包编译 SQLite 。这在 Windows 上特别有用,因为在 Windows 中查找系统库非常困难。

添加后,你现在可以访问 main.rs 中的 rusqlite,将现有代码替换为以下内容:

use rusqlite::{Connection, Result};

fn main() -> Result<(), Box<dyn std::error::Error>> {
  let conn = Connection::open("notes.db")?;
  conn.execute(
    "create table if not exists notes (
      id integer primary key,
      body text not null unique
    )"
,
    [],
  )?;
  
  Ok(())
}

现在你可以运行 cargo run ,一旦它构建完成,notes 程序就会运行并立即退出。然后,你应该会在当前目录中看到一个名为 notes.db 的文件。如果你安装了 DB 浏览器,你可以打开这个文件并看到一个带有 id 和 body 列的 notes 表。

如果你再次运行该程序,则不会发生任何事情。我们运行的 SQL[7] 命令如下:

create table if not exists notes (
  id integer primary key,
  body text not null unique
)

指定仅在表不存在时创建表。当我们用 let conn = Connection::open("notes.db")?; 打开连接时,我们将 rusqlite 库指向同一个数据库文件,它能够确定该表已经创建。

可选的为 SQLite 安装数据库浏览器

我发现能够直观地确认事情按预期工作很有帮助。此时,你可以下载一个数据库浏览器,让你可以查看新创建的 notes.db 文件的内容。

你可以下载 SQLite 的数据库浏览器[8]。安装后打开它并单击 Open Database 。

notes.db 在 SQLite 的 DB Browser 中打开

现在我们已经创建了一个表并设置了我们的 schema,我们可以继续添加我们的第一条笔记。

03 CRUD——创建、读取、更新、删除

在任何程序中开发新功能时,我通常独立处理 CRUD 首字母缩略词的每个组件。对我来说最有意义的构建顺序是:

  1. Create

  2. Read

  3. Delete

  4. Update

本教程的这一部分将仅涵盖 create。下篇文章将讨论其他的。

04 创建

Create 是第一位的,因为没有它,其他一切都没有意义。在很多情况下,你的应用程序只要可以创建就可以运行。你显然希望能够完成其他工作,但他们的缺失不会影响你创建新项目。在我们的笔记示例中,你会看到,即使你刚刚添加了创建功能,你仍然拥有将笔记正确存储到本地数据库的程序。如果这就是你能够完成的全部工作,你仍然可以使用 DB Browser for SQLite 之类的工具打开 sqlite3 数据库并在那里浏览所有笔记。

在我们这里的小示例中,CRUD 的其余部分没有太多内容,但是在构建更大的图形用户界面时,仅演示和测试创建功能会很有用。

要求

对于笔记的创建,我只是希望能够在终端中输入我的笔记并按回车键。为了从命令行获得输入,我们将使用内置的 std::io[9] 包。

use std::io;
use rusqlite::{Connection, Result};

fn main() -> Result<(), Box<dyn std::error::Error>> {
  let conn = Connection::open("notes.db")?;
  conn.execute(
    "create table if not exists notes (
      id integer primary key,
      body text not null unique
    )"
,
    [],
  )?;

  let mut buffer = String::new();
  io::stdin().read_line(&mut buffer)?;
  conn.execute("INSERT INTO notes (body) values (?1)", [buffer])?;

  Ok(())
}

我们可以再次使用 cargo run 运行我们的应用程序,你现在应该看到它不会立即退出。你可以输入任何你喜欢的消息,但 “hello world” 是标准的 “这东西工作正常吗” 消息。按回车键后,程序应该退出。

提交第一个注释后的示例输出

如果你在上面安装了 DB Browser,你现在可以单击 Browse Data,你将看到一行 id: 1 和 body: “hello world”(或你刚刚输入的任何内容)。

DB 浏览器显示“hello world”注释

这很好,但程序的目的是让我们快速创建许多笔记。我们需要一些方法来使程序在提交第一个注释后不会立即退出。

为了实现这一点,我们将使用一个循环——这里用一个 while 循环。

use std::io;
use rusqlite::{Connection, Result};

fn main() -> Result<(), Box<dyn std::error::Error>> {
  let conn = Connection::open("notes.db")?;
  conn.execute(
    "create table if not exists notes (
      id integer primary key,
      body text not null unique
    )"
,
    [],
  )?;

  let mut running = true;
  while running == true {
    let mut buffer = String::new();
    io::stdin().read_line(&mut buffer)?;
    let trimmed_body = buffer.trim();
    if trimmed_body == "" {
      running = false;
    } else {
      conn.execute("INSERT INTO notes (body) values (?1)", [trimmed_body])?;
    }
  }

  Ok(())
}

为此,我们引入了一个名为 running 的布尔变量。当我们启动程序时,我们希望继续接受输入,因此我们将其初始化为 true。

trimmed_body = buffer.trim(); 删除输入行末尾的任何空格。这是必要的,因为 read_line 返回带有换行符 \n 的字符串。这在你可能会看到的大多数地方都是不可见的,但需要使 trimmed_body == "" 的相等检查正确工作。作为一个额外的好处,.trim() 确保在我们存储到数据库之前删除任何尾随空格。

我们现在可以再次运行 cargo run,你现在应该可以在不退出程序的情况下输入一个又一个的字符。写完笔记后,你可以在空行后再按 Enter 键(即按两次 Enter 键),程序将退出。

单次运行期间创建的多个笔记

05 总结

正如开头提到的,这篇文章是一个较长系列的开始,该系列将介绍如何使用 Rust 为命令行构建和设计笔记应用程序。在过去的一年里,我使用了一个简单的笔记应用程序作为我用来试验新技术的项目。到目前为止,我已经使用Swift iOS[10]React Native for Android[11]React[12] 和 Vanilla JavaScript 采用上述类似的方法构建了笔记应用程序(亲切地称为 engram[13])。

我现在正在记录这个过程,因为事实证明它非常成功地让我了解我需要知道的东西。

希望你通过本系列,自己动手试验,能够对 Rust 有更好的掌握。

原文链接:https://devtails.xyz/how-to-build-a-note-taking-command-line-application-with-rust

参考资料

[1]

engram: https://github.com/adamjberg/engram

[2]

cargo 是 Rust 包管理器: https://doc.rust-lang.org/cargo/

[3]

The Cargo Book: https://doc.rust-lang.org/cargo/reference/manifest.html

[4]

cargo run: https://doc.rust-lang.org/cargo/commands/cargo-run.html

[5]

请参阅 Cargo Book: https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html

[6]

sqlite3: https://www.sqlite.org/index.html

[7]

SQL: https://www.w3schools.com/sql/

[8]

下载 SQLite 的数据库浏览器: https://sqlitebrowser.org/dl/

[9]

std::io: https://doc.rust-lang.org/std/io/index.html

[10]

Swift iOS: https://apps.apple.com/ca/app/engram/id1568952668

[11]

React Native for Android: https://play.google.com/store/apps/details?id=com.xyzdigital.engram

[12]

React: https://engram.xyzdigital.com/signup

[13]

engram: https://engramhq.xyz/




往期推荐


我是 polarisxu,北大硕士毕业,曾在 360 等知名互联网公司工作,10多年技术研发与架构经验!2012 年接触 Go 语言并创建了 Go 语言中文网!著有《Go语言编程之旅》、开源图书《Go语言标准库》等。


坚持输出技术(包括 Go、Rust 等技术)、职场心得和创业感悟!欢迎关注「polarisxu」一起成长!也欢迎加我微信好友交流:gopherstudio

浏览 65
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报