学习了 Rust 的语法后,我对这门语言感觉良好。于是计划实战一下。这篇文章是系列文章中的第一篇,该系列将介绍如何使用 Rust 构建笔记应用程序。
最终目标是为读者提供足够的知识来构建自己的类似于 engram[1] 的笔记应用程序。
本系列内容希望读者具备一些编程知识,但可能是 Rust 新手。该系列的每个部分都将产生一个功能性和有用的应用程序,将在以后的文章中构建。
我强烈建议手动敲入你看到的代码,而不仅仅是复制和粘贴。这是获得更好理解的最有效方法之一。
从 Rust 官网找到入门页面:https://www.rust-lang.org/learn/get-started,根据介绍安装 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 中忽略目标文件夹。
cargo run
cargo run[4] 命令构建并运行当前的包。在本教程进行更改后,你将使用它来运行和测试这些更改。运行一次后,你会看到更多的文件和文件夹出现。
Cargo.lock 这是一个自动生成的文件,它准确指定了正在使用的库的版本。有关 Cargo.toml 与 Cargo.lock 的更多详细信息,请参阅 Cargo Book[5]。
target 这个文件夹是存储所有构建文件的地方。你几乎可以忽略这一点,因为 cargo 工具会根据需要处理它。
先从数据库开始。
数据库是几乎所有应用程序的核心。大多数新功能需要存储一些新数据或以某种方式检索现有信息。由于这些原因,这通常是你在构建新事物时应该考虑的第一件事。特别是,我试图构建的 “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 库指向同一个数据库文件,它能够确定该表已经创建。
我发现能够直观地确认事情按预期工作很有帮助。此时,你可以下载一个数据库浏览器,让你可以查看新创建的 notes.db 文件的内容。
你可以下载 SQLite 的数据库浏览器[8]。安装后打开它并单击 Open Database 。
现在我们已经创建了一个表并设置了我们的 schema,我们可以继续添加我们的第一条笔记。
在任何程序中开发新功能时,我通常独立处理 CRUD 首字母缩略词的每个组件。对我来说最有意义的构建顺序是:
Create
Read
Delete
Update
本教程的这一部分将仅涵盖 create。下篇文章将讨论其他的。
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”(或你刚刚输入的任何内容)。
这很好,但程序的目的是让我们快速创建许多笔记。我们需要一些方法来使程序在提交第一个注释后不会立即退出。
为了实现这一点,我们将使用一个循环——这里用一个 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 键),程序将退出。
正如开头提到的,这篇文章是一个较长系列的开始,该系列将介绍如何使用 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
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