Rust 項(xiàng)目實(shí)戰(zhàn):從頭構(gòu)建一個(gè)筆記命令行程序(1)
學(xué)習(xí)了 Rust 的語(yǔ)法后,我對(duì)這門語(yǔ)言感覺(jué)良好。于是計(jì)劃實(shí)戰(zhàn)一下。這篇文章是系列文章中的第一篇,該系列將介紹如何使用 Rust 構(gòu)建筆記應(yīng)用程序。
最終目標(biāo)是為讀者提供足夠的知識(shí)來(lái)構(gòu)建自己的類似于 engram[1] 的筆記應(yīng)用程序。
本系列內(nèi)容希望讀者具備一些編程知識(shí),但可能是 Rust 新手。該系列的每個(gè)部分都將產(chǎn)生一個(gè)功能性和有用的應(yīng)用程序,將在以后的文章中構(gòu)建。
我強(qiáng)烈建議手動(dòng)敲入你看到的代碼,而不僅僅是復(fù)制和粘貼。這是獲得更好理解的最有效方法之一。
01 入門
從 Rust 官網(wǎng)找到入門頁(yè)面:https://www.rust-lang.org/learn/get-started,根據(jù)介紹安裝 Rust。
安裝 Rust
curl?--proto?'=https'?--tlsv1.2?-sSf?https://sh.rustup.rs?|?sh
創(chuàng)建新的 Rust 項(xiàng)目
cargo?new?notes
cargo 是 Rust 包管理器[2]。它支持多種命令,本文將介紹其中的幾個(gè)。cargo new 在當(dāng)前目錄中創(chuàng)建一個(gè)名為 notes(或你指定的任何其他內(nèi)容)的新項(xiàng)目(和文件夾)。
src/main.rs 包含一個(gè)簡(jiǎn)單的“Hello World”應(yīng)用程序。
Cargo.toml 被稱為清單。請(qǐng)參閱 The Cargo Book[3] 以深入了解此處支持的內(nèi)容。我們只需要在這里做一個(gè)小的調(diào)整來(lái)添加一個(gè)依賴項(xiàng),所以不要太擔(dān)心現(xiàn)在不理解全部?jī)?nèi)容。
默認(rèn)情況下,.gitignore 可以方便地從 git 中忽略目標(biāo)文件夾。
運(yùn)行 hello world 應(yīng)用程序
cargo?run
cargo run[4] 命令構(gòu)建并運(yùn)行當(dāng)前的包。在本教程進(jìn)行更改后,你將使用它來(lái)運(yùn)行和測(cè)試這些更改。運(yùn)行一次后,你會(huì)看到更多的文件和文件夾出現(xiàn)。
Cargo.lock 這是一個(gè)自動(dòng)生成的文件,它準(zhǔn)確指定了正在使用的庫(kù)的版本。有關(guān) Cargo.toml 與 Cargo.lock 的更多詳細(xì)信息,請(qǐng)參閱 Cargo Book[5]。
target 這個(gè)文件夾是存儲(chǔ)所有構(gòu)建文件的地方。你幾乎可以忽略這一點(diǎn),因?yàn)?cargo 工具會(huì)根據(jù)需要處理它。

02 準(zhǔn)備數(shù)據(jù)模型
先從數(shù)據(jù)庫(kù)開(kāi)始。
創(chuàng)建一個(gè) sqlite3 數(shù)據(jù)庫(kù)
數(shù)據(jù)庫(kù)是幾乎所有應(yīng)用程序的核心。大多數(shù)新功能需要存儲(chǔ)一些新數(shù)據(jù)或以某種方式檢索現(xiàn)有信息。由于這些原因,這通常是你在構(gòu)建新事物時(shí)應(yīng)該考慮的第一件事。特別是,我試圖構(gòu)建的 “schema” 是什么。
就本教程而言,我們的 schema 非常簡(jiǎn)單。我們想要一個(gè)帶有 id 列和 body 列的 notes 表。id 存儲(chǔ)特定筆記的唯一標(biāo)識(shí)符。這是大多數(shù)數(shù)據(jù)庫(kù)的必填字段,因?yàn)樗试S你直接引用現(xiàn)有項(xiàng)目。body 列將存儲(chǔ)我們正在保存的筆記的內(nèi)容。歡迎你在這里選擇其他一些你覺(jué)得更好的術(shù)語(yǔ)。一些可能的選擇:內(nèi)容、消息、文本或標(biāo)題。事后當(dāng)然可以改變這一點(diǎn),但隨著時(shí)間的推移,越來(lái)越多的代碼引用這些特定術(shù)語(yǔ),改變會(huì)越來(lái)越困難,所以試著選擇一個(gè)你能接受的并堅(jiān)持下去。
我現(xiàn)在看到的大多數(shù)教程主要關(guān)注在服務(wù)器上存儲(chǔ)數(shù)據(jù)并通過(guò)某種 API 同步它。這個(gè)系列最終會(huì)那樣實(shí)現(xiàn),但像我們正在構(gòu)建的筆記應(yīng)用程序離線工作是非常重要的。盡早設(shè)置此限制允許我們考慮離線構(gòu)建——而不是試圖將其綁定到現(xiàn)有的云應(yīng)用程序。
sqlite3[6] 是一個(gè)流行的數(shù)據(jù)庫(kù)庫(kù),它將數(shù)據(jù)庫(kù)存儲(chǔ)在文件系統(tǒng)上的單個(gè)文件中。這對(duì)用戶來(lái)說(shuō)很簡(jiǎn)單,因?yàn)樗麄儾恍枰\(yùn)行單獨(dú)的數(shù)據(jù)庫(kù)服務(wù)器,并且如果需要,數(shù)據(jù)庫(kù)文件可以傳遞到其他系統(tǒng)。
創(chuàng)建筆記表
第一步是創(chuàng)建一個(gè)表來(lái)存放我們的應(yīng)用程序數(shù)據(jù)。我們將使用 rusqlite 庫(kù)來(lái)處理我們與 sqlite 數(shù)據(jù)庫(kù)的連接。你可以通過(guò)修改 Cargo.toml 來(lái)安裝它。
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"]
這會(huì)添加 rusqlite 作為依賴項(xiàng),下次嘗試構(gòu)建時(shí)將安裝該依賴項(xiàng)。features = ["bundled"] 告訴包編譯 SQLite 。這在 Windows 上特別有用,因?yàn)樵?Windows 中查找系統(tǒng)庫(kù)非常困難。
添加后,你現(xiàn)在可以訪問(wèn) main.rs 中的 rusqlite,將現(xiàn)有代碼替換為以下內(nèi)容:
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(())
}
現(xiàn)在你可以運(yùn)行 cargo run ,一旦它構(gòu)建完成,notes 程序就會(huì)運(yùn)行并立即退出。然后,你應(yīng)該會(huì)在當(dāng)前目錄中看到一個(gè)名為 notes.db 的文件。如果你安裝了 DB 瀏覽器,你可以打開(kāi)這個(gè)文件并看到一個(gè)帶有 id 和 body 列的 notes 表。
如果你再次運(yùn)行該程序,則不會(huì)發(fā)生任何事情。我們運(yùn)行的 SQL[7] 命令如下:
create?table?if?not?exists?notes?(
??id?integer?primary?key,
??body?text?not?null?unique
)
指定僅在表不存在時(shí)創(chuàng)建表。當(dāng)我們用 let conn = Connection::open("notes.db")?; 打開(kāi)連接時(shí),我們將 rusqlite 庫(kù)指向同一個(gè)數(shù)據(jù)庫(kù)文件,它能夠確定該表已經(jīng)創(chuàng)建。
可選的為 SQLite 安裝數(shù)據(jù)庫(kù)瀏覽器
我發(fā)現(xiàn)能夠直觀地確認(rèn)事情按預(yù)期工作很有幫助。此時(shí),你可以下載一個(gè)數(shù)據(jù)庫(kù)瀏覽器,讓你可以查看新創(chuàng)建的 notes.db 文件的內(nèi)容。
你可以下載 SQLite 的數(shù)據(jù)庫(kù)瀏覽器[8]。安裝后打開(kāi)它并單擊 Open Database 。

現(xiàn)在我們已經(jīng)創(chuàng)建了一個(gè)表并設(shè)置了我們的 schema,我們可以繼續(xù)添加我們的第一條筆記。
03 CRUD——?jiǎng)?chuàng)建、讀取、更新、刪除
在任何程序中開(kāi)發(fā)新功能時(shí),我通常獨(dú)立處理 CRUD 首字母縮略詞的每個(gè)組件。對(duì)我來(lái)說(shuō)最有意義的構(gòu)建順序是:
Create
Read
Delete
Update
本教程的這一部分將僅涵蓋 create。下篇文章將討論其他的。
04 創(chuàng)建
Create 是第一位的,因?yàn)闆](méi)有它,其他一切都沒(méi)有意義。在很多情況下,你的應(yīng)用程序只要可以創(chuàng)建就可以運(yùn)行。你顯然希望能夠完成其他工作,但他們的缺失不會(huì)影響你創(chuàng)建新項(xiàng)目。在我們的筆記示例中,你會(huì)看到,即使你剛剛添加了創(chuàng)建功能,你仍然擁有將筆記正確存儲(chǔ)到本地?cái)?shù)據(jù)庫(kù)的程序。如果這就是你能夠完成的全部工作,你仍然可以使用 DB Browser for SQLite 之類的工具打開(kāi) sqlite3 數(shù)據(jù)庫(kù)并在那里瀏覽所有筆記。
在我們這里的小示例中,CRUD 的其余部分沒(méi)有太多內(nèi)容,但是在構(gòu)建更大的圖形用戶界面時(shí),僅演示和測(cè)試創(chuàng)建功能會(huì)很有用。
要求
對(duì)于筆記的創(chuàng)建,我只是希望能夠在終端中輸入我的筆記并按回車鍵。為了從命令行獲得輸入,我們將使用內(nèi)置的 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 運(yùn)行我們的應(yīng)用程序,你現(xiàn)在應(yīng)該看到它不會(huì)立即退出。你可以輸入任何你喜歡的消息,但 “hello world” 是標(biāo)準(zhǔn)的 “這東西工作正常嗎” 消息。按回車鍵后,程序應(yīng)該退出。

如果你在上面安裝了 DB Browser,你現(xiàn)在可以單擊 Browse Data,你將看到一行 id: 1 和 body: “hello world”(或你剛剛輸入的任何內(nèi)容)。

這很好,但程序的目的是讓我們快速創(chuàng)建許多筆記。我們需要一些方法來(lái)使程序在提交第一個(gè)注釋后不會(huì)立即退出。
為了實(shí)現(xiàn)這一點(diǎn),我們將使用一個(gè)循環(huán)——這里用一個(gè) while 循環(huán)。
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(())
}
為此,我們引入了一個(gè)名為 running 的布爾變量。當(dāng)我們啟動(dòng)程序時(shí),我們希望繼續(xù)接受輸入,因此我們將其初始化為 true。
讓 trimmed_body = buffer.trim(); 刪除輸入行末尾的任何空格。這是必要的,因?yàn)?read_line 返回帶有換行符 \n 的字符串。這在你可能會(huì)看到的大多數(shù)地方都是不可見(jiàn)的,但需要使 trimmed_body == "" 的相等檢查正確工作。作為一個(gè)額外的好處,.trim() 確保在我們存儲(chǔ)到數(shù)據(jù)庫(kù)之前刪除任何尾隨空格。
我們現(xiàn)在可以再次運(yùn)行 cargo run,你現(xiàn)在應(yīng)該可以在不退出程序的情況下輸入一個(gè)又一個(gè)的字符。寫完筆記后,你可以在空行后再按 Enter 鍵(即按兩次 Enter 鍵),程序?qū)⑼顺觥?/p>
05 總結(jié)
正如開(kāi)頭提到的,這篇文章是一個(gè)較長(zhǎng)系列的開(kāi)始,該系列將介紹如何使用 Rust 為命令行構(gòu)建和設(shè)計(jì)筆記應(yīng)用程序。在過(guò)去的一年里,我使用了一個(gè)簡(jiǎn)單的筆記應(yīng)用程序作為我用來(lái)試驗(yàn)新技術(shù)的項(xiàng)目。到目前為止,我已經(jīng)使用Swift iOS[10]、React Native for Android[11]、React[12] 和 Vanilla JavaScript 采用上述類似的方法構(gòu)建了筆記應(yīng)用程序(親切地稱為 engram[13])。
我現(xiàn)在正在記錄這個(gè)過(guò)程,因?yàn)槭聦?shí)證明它非常成功地讓我了解我需要知道的東西。
希望你通過(guò)本系列,自己動(dòng)手試驗(yàn),能夠?qū)?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]請(qǐng)參閱 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 的數(shù)據(jù)庫(kù)瀏覽器: 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,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗(yàn)!2012 年接觸 Go 語(yǔ)言并創(chuàng)建了 Go 語(yǔ)言中文網(wǎng)!著有《Go語(yǔ)言編程之旅》、開(kāi)源圖書《Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)》等。
堅(jiān)持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場(chǎng)心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長(zhǎng)!也歡迎加我微信好友交流:gopherstudio
