Rust 學習筆記:快速上手篇

2023-07-17 15:00:19

Rust 學習筆記:快速上手篇

這篇學習筆記將用於記錄本人在快速上手 Rust 程式語言時所記錄的學習心得與程式碼範例。為此,我會在本筆記庫專案的Programming/LanguageStudy/目錄下建立一個名為Rust的目錄,並在該目錄下設定以下兩個子目錄:

  • QuickStart目錄用於存放 Markdown 格式的筆記。
  • examples目錄則用於存放筆記中所編寫的程式碼範例。

參考資料說明

背景知識介紹

Rust 是一門由 Mozilla 公司主導開發的通用、編譯型程式設計語言。這門語言的設計準則為「安全、並行、實用」,支援函數式、並行式、命令式等多種程式設計正規化,目前被認為是自 C++ 以來最全能的系統級程式設計語言。它為系統程式設計領域提供了一種更安全、更快速且原生支援並行的現代化解決方案,因而具有相當的學習價值。當然了,按照學習一門程式語言的慣例,在進入具體的學習階段之前,我們需要做一些知識準備工作。首先,讓我們來了解一下該語言的起源故事,以便之後的學習過程中更好地理解它的設計思想。

語言的起源故事

Rust 專案 最初是 Mozilla 公司員工、主要負責編譯器實現的語言工程師 Graydon Hoare 在 2006 年開發的一個私人試驗專案。眾所周知,在上個世紀 70 年代末和 80 年代初,程式語言領域出現了許多優秀的設計理念,但這些理念中的許多設計都受限於當時的技術條件,並沒有機會被轉化成得到廣泛使用的系統級語言,Graydon Hoare 希望能在該專案中通過結合現代程式語言的相關理論來重新設計一門系統級程式語言,目標是讓開發者們在系統程式設計領域中能在得到比 C/C++更好的編碼體驗的同時,避開 Java/C# 這類語言因其自身附帶的大型垃圾回收機制而產生的記憶體開銷。關於這方面的詳細資訊,讀者可參考InfoQ 在 2012 年對 Graydon Hoare 本人的專訪。至於 Mozilla 公司本身,則是從 2009 年開始贊助這個專案的,他們成立了專門的團隊來支援 Rust 的開發,並開始將其應用於實驗性的 Servo 瀏覽器引擎專案。

2010 年之後,Rust 專案正式向公眾釋出產品,並於同年開始改寫自託管編譯器(基於 LLVM 後端)來替換原來用 OCaml 寫的編譯器。其釋出方式主要分為 stable、beta、nightly 三個版本系列。其中,nightly 版本系列會每天迭代一次,主要用於開發者針對 Rust 語言的最新特性進行學習和研究。而 beta 版本系列則是針對一些相對成熟的特性發布的測試版本,每六週迭代一次,通常用於前瞻性的試驗性開發。然後,beta 系列的版本在通過了六週的測試之後,就會被合併到 stable 版本系列中,後者是主要用於實際生產環境的穩定版本。基本上,Rust 專案的釋出流程就像火車一樣按照時刻表井然有序。

nightly: * - - * - - * - - * - - * - - * - * - *
                     |                         |
beta:                * - - - - - - - - *       *
                                       |
stable:                                *

值得一提的是,Rust 這個名字在英文中的原意指的是一種生命力很強的真菌,Graydon Hoare 希望 Rust 語言可以覆蓋在死去語言的殘骸之上,吸收這些舊語言的優秀特性,像真菌一樣擁有多種可以互相轉化的生命形態 [32]。Rust 的名字從字形上糅合了 Trust 和 Robust,也暗示了「信任」和「魯棒性」。目前來看,Rust 確實擁有了極強的生命力,讓我們期待一下 Rust 的永不休眠。

開源與社群生態

雖然 Rust 專案的主要資金來自 Mozilla 公司的資助,但本質上依然是一個由開發者社群共有的開源專案,它相當大一部分程式碼來自於開源社群的貢獻者。因此,該專案有著完善的提案流程(RFC,Request For Comments),每一個人都可以提交提案,進行開發工作的核心團隊細分為專項治理語言專案、社群運營、語言核心開發、工具開發、庫開發等,來管理維護各個專案的各方面事項。

最近,由於受到新冠肺炎疫情的影響,Mozilla 公司在 2020 年 8 月宣佈裁員四分之一,這其中包括了 Rust 開發團隊,這顯然是非常令人惋惜的。但塞翁失馬焉知非福,由於 Rust 開發團隊的專家們被裁後加入了 Facebook、Amazon、Google 等科技巨頭公司,客觀上或許也會更有利於 Rust 語言的推廣,並獲得更多資源支援。例如,Rust 基金會在2021 年 2 月宣佈成立,其贊助商中就包括了華為、AWS、Google、Microsoft、Facebook 等一眾計算機科技界的巨頭企業,在這裡,筆者真心希望該基金會能專注於促進 Rust 語言的開發與生態發展。

語言的優劣分析

正如 Graydon Hoare 在之前提到的那次採訪中所說的,Rust 語言的目標群體是「被 C++ 所困擾的開發者」,它希望幫助人們以更簡單明瞭的方式來設計安全且快速的系統級應用程式。這意味著,這門語言應該要既可用於實現針對機器底層的具體操作,也可用於完成針對業務邏輯的抽象設計。具體來說,開發者們可以通過學習和使用 Rust 這門程式語言來讓自己在工作中獲得以下能力:

  • 可使用更有效率的程式設計工具鏈:Rust 語言為開發者們提供了得到了精心維護且便於查詢的官方檔案、以及支援多種編輯器的完整工具鏈,其中提供了語法錯誤提示、智慧自動補全、自動型別監測、自動格式化、包管理以及編譯與構建等功能,使得開發者們可以在不使用任何第三方工具的情況下,輕鬆利用這一工具鏈進行高效的程式設計工作。
  • 可進行更方便的多執行緒開發:Rust 語言獨特的所有權機制和記憶體安全的特性也為實現沒有資料競爭的並行提供了語言層面上的原生支援。
  • 可開發面向 WebAssembly 應用程式:WebAssembly 的出現解決了人們希望在 Web 瀏覽器、嵌入式裝置等環境中執行計算密集型操作的需求。而用 Rust 語言編寫的程式碼可以被直接編譯成 WebAssembly 程式,這樣就能確保程式在上述執行環境中擁有與原生程式碼相似的執行速度。
  • 可編寫更安全的應用程式:與 C/C++ 語言相比,Rust 語言可以利用其所有權、生命週期等特性來幫助開發者們在語言層面上去解決記憶體管理、資料佈局等系統程式設計難題。
  • 可獲得更優秀的執行效能:與 Java/C# 等語言相比,Rust 語言對記憶體安全的承諾並不依賴於龐大的執行時環境(Runtime Environment)與垃圾回收器機制(GC),這種遵循零成本抽象(Zero-Cost Abstraction)規則的設計在賦予了開發者更多底層操作能力的同時,也提高了應用程式的執行效能。

當然了,Rust 語言也是存在一些不足的,例如學習曲線陡峭就是一個經常被開發者們抱怨的問題。在 2020 年的一份調查報告中,約 15.8% 的開發者認為「如果 Rust 更好學一些,使用它的人會應該更多」,其中生命週期、所有權等概念對於初學者來說可能難以理解,特別是「生命週期」,有 61.4% 的反饋者認為難以理解或學習困難。除此之外,編譯時間較長也是 Rust 社群一直在討論的問題。在上面提到的那份報告中,約 50.5% 開發者感覺已經有一定的提升,但縮短 Rust 的編譯時間仍然是 Rust 團隊接下來的重要任務。

然而,這些不足並不妨礙 Rust 成為近年來最為熱門的程式語言之一。根據 Stack Overflow 對開發者的調研顯示,雖然只有 7% 的開發者表示自己正在使用 Rust,對比 JavaScript、Python 等語言,使用 Rust 的開發者佔比並不高;但從 2016 年開始,Rust 每年都是開發者們最喜愛的程式語言。下面,讓我們繼續來了解一下能讓該語言的上述優勢得到發揮的主要領域:

  • 學術研究:Rust 語言在計算機專業領域中是一個很好的學術研究工具。例如,人們可以學習如何使用 Rust 語言開發作業系統,這個學習過程將幫助他們更好地理解作業系統中的各種概念。
  • 團隊合作:對於開發者團隊來說, Rust 語言是非常實用的工具。眾所周知,低水平的程式程式碼會包含很多 bug,需要測試人員進行覆蓋測試廣泛驗證。然而在 Rust 語言中,如果程式程式碼中包含 bug,編譯器將拒絕編譯程式碼,這樣一來,開發者就可以更專注於程式的業務邏輯了。
  • 商業開發:到目前為止,已經有不少大大小小的公司選擇使用 Rust 語言來完成各種商業開發的任務。這些任務包括命令列工具,Web 服務,DevOps 工具,嵌入式裝置,音訊和視訊的分析和轉碼,加密貨幣,生物資訊學,搜尋引擎,物聯網應用,機器學習,甚至是火狐瀏覽器的重要組成部分。
  • 開源運動:Rust 本身就是一門遵守 Apache 和 MIT 開源許可證的程式設計語言,這意味人們可以按照這些許可證賦予的權益參與各種 Rust 語言設計與推廣的公益活動。

應用現狀及案例

就目前的情況來看,安全問題通常是商業公司考慮採用 Rust 語言的首要原因,Rust 在沒有犧牲效能的情況下提供了比 C/C++ 安全的系統程式設計,使其成為了更好的選擇。但根據 JetBrains 2021 年的調研報告,出於興趣或為私人專案選擇 Rust 的開發者仍然佔大多數,真正用於工作的開發者僅佔 16%,而 Go 語言用於工作的開發者比例佔到了 61%,差距明顯。為 Rust 專案提供支援、給 Rust 開發者創造更多工作機會也是 Rust 基金會的目標之一。

下面,讓我們來介紹一些知名的計算機科技公司對 Rust 語言的具體應用情況。

  • Google:該公司於 2016 年開始開發一個名為 Fuchsia 作業系統,該專案在 2020 年 12 月首次亮相於 Google Open Source,專案中有 22% 的程式碼是使用 Rust 語言來編寫的。
  • Microsoft:該公司在 2019 年 2 月釋出報告稱其產品中 70% 的安全問題為記憶體安全問題,為了解決安全問題,開始嘗試使用 Rust 來代替 C/C++ 重寫 Windows 元件;除此之外,Microsoft 的 DeisLabs 團隊也選擇 Rust 來構建 Kubernetes 工具 Krustlet,並稱 Rust 比 Go 更適合 Kubernetes 的開發。
  • Meta:該公司最早使用 Rust 語言的專案可追溯到 2016 年。當時,Facebook 的原始碼管理團隊啟動了一個名為 Mononoke 的重寫專案,目標是改善公司內部使用的原始碼管理工具,以便更好地服務於 Facebook 的數千名開發人員和自動化流程。當時,Facebook 的後端程式碼庫非常依賴於 C++,這意味著 Mononoke 預設情況下應該用 C++來實現。但是源控制團隊需要考慮源控制後端的可靠性需求。因為當損壞或停機可能導致服務中斷時,可靠性顯然是重中之重。這就是為什麼團隊選擇在 C++ 程式碼庫上使用 Rust 語言的原因。Mononoke 專案的生產實踐,證明了 Rust 的實用性。
  • Amazon:該公司於 2017 年開始在其提供的部分服務中採用了 Rust 語言,例如 Firecracker、Botterocket 等。
  • PingCAP:該公司於 2016 年 1 月開始使用 Rust 語言來開發底層分散式儲存 TIKV,並於 2016 年 4 月宣佈將該專案開源。
  • 位元組跳動:其遠端辦公系統「飛書」的開發團隊從 2017 年開始在該專案中採用 Rust 語言,該系統在使用者端的非 UI 部分就是依靠 Rust 語言來實現跨平臺的。除此之外,該公司在 2021 年 5 月宣佈開源的 rsmpeg 專案,也是選擇了 Rust 來作為其主要的開發語言。

總而言之,Rust 語言的設計側重於記憶體安全、零成本抽象和實用性,尤其是它致力於解決的記憶體安全問題一直以來是系統開發的痛點,傳統領域如網路、資料庫對於安全要求越來越高,新興領域如區塊鏈天然對於安全性就有極高的要求,因此 Rust 這門新的程式語言才能迅速獲得國內外公司的青睞,成為 C/C++ 的有力競爭者。

開發環境設定

由於 Rust 的開發環境是基於 C/C++ 編譯工具來構建的,這意味著在安裝語言環境之前,我們的計算機上至少需要先安裝一個 C/C++ 編譯工具。換句話說,如果該計算機上使用的是類 UNIX 系統,它就需要先安裝 GCC 或 clang。如果是 macOS 系統,則就需要先安裝 Xcode。如果是 Windows 系統,則需要先安裝 MinGW 之類的 C/C++ 編譯工具。

安裝開發工具

到目前為止,Rust 語言的開發工具大體上都是通過自動化指令碼的形式來安裝的。所以在確定安裝了 C/C++ 編譯工具之後,` 接下來要做的就是根據自己所在的作業系統來下載並執行用於安裝 Rust 語言開發工具的指令碼檔案了。

  • 如果想要在 Windows 系統下學習 Rust 語言,我們只需在搜尋引擎中搜尋「rust」關鍵字或直接在 Web 瀏覽器的位址列中輸入https://www.rust-lang.org/這個 URL 即可進入到 Rust 專案的官方網站,然後通過點選首頁中的「install」連結,就會看到如下頁面:

    在該頁面中,我們可以根據自己所在的平臺來下載 32 位或 64 位版本的安裝指令碼(通常是一個名為rustup-init.exe的二進位制檔案)。待下載完成之後,我們就可以雙擊執行該指令碼檔案,並遵循螢幕上的指示來安裝(對於初學者,只需一路選擇預設選項即可)。

  • 如果想在類 UNIX 的系統環境(包括 WSL 環境)下學習 Rust 語言,我們只需直接在 Bash 之類的終端環境中執行下面的 Shell 程式碼,並遵循終端返回的指示資訊來安裝(對於初學者,只需一路選擇預設選項即可)。

    # 可選步驟:設定國內下載映象
    echo "export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static" >> ~/.bashrc
    echo "export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup" >> ~/.bashrc
    source .bashrc
    
    # 第一步:下載並執行安裝指令碼
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    
    # 第二步:安裝完畢後重新整理環境變數
    source ~/.cargo/env
    

需要注意的是,我們在預設情況下安裝的 Rust 開發環境是屬於 stable 這一版本系列的。正如之前所說,這一系列的版本每六週迭代一次,在各方面的設定上都會偏向於確保生產環境的穩定性。但如果我們想使用 racer 這一類使用某些新特性的元件,就必須切換到設定方案更偏向於學習、研究性質的 nightly 版本。對於 Rust 開發工具的版本管理操作,我們通常需要用到 rustup 這一命令列工具。下面來示範一下該工具的具體使用方式。

# rustup install <版本號> 或 <版本系列名稱>
# 如果想安裝 1.68.0 版本的 Rust 語言環境,可執行命令:
rustup install 1.68.0
# 如果想安裝語言環境的 nightly 系列中的最新版本,可執行命令:
rustup install nightly
# 如果想設定語言環境的預設版本,可執行命令:
rustup default nightly
# 如果想更新當前語言環境的版本,可執行命令:
rustup update
# 如果想刪除當前版本的語言環境,可執行命令:
rustup self uninstall

待成功執行開發工具的安裝操作之後,我們可通過在終端中執行rustc -V命令來驗證安裝狀態。如果該命令輸出了相應的版本資訊,就證明 Rust 開發工具的安裝和設定工作已經成功了一半了,接下來的任務是設定 Cargo 包管理器,並使用它來安裝一些外掛。

設定包管理器

在面向 Rust 語言的程式設計活動中,我們的專案通常是以「包」的形式來進行組織和管理的,目前 Rust 社群中共註冊有 6.8 萬個包,其中包括了該語言的基礎庫和各種第三方框架,總下載次數達到 95 億次,下載次數較多的包涉及數值、解析器、字元解碼、FFi 開發、序列化/反序列化等。關於這些包的管理與構建操作,我們通常需要用到 Cargo 這個命令列工具(這是一個內建在 Rust 開發工具中的包管理器)。下面,我們來具體介紹一下這個工具的具體方法、

如前所述,在開發 Rust 專案的過程中,我們通常需要使用 Cargo 包管理器來下載並管理當前專案所依賴的包,以及完成專案的構建工作。而 Cargo 在預設情況下連結的是https://crates.io/這個官方的程式倉庫。由於眾所周知的原因,這個伺服器位於國外的官方倉庫在可用性上非常容易受到各種不可抗力的影響,因此在正式使用該包管理器之前,我們最好還是先將其設定為位於國內的代理倉庫。為此,讀者需要將代理倉庫的伺服器地址寫到 Cargo 的組態檔中(該組態檔就叫「config」,沒有擴充套件名,通常情況下位於${HOME}/.cargo/目錄中)。在這裡,筆者將其具體設定如下:

[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
# 指定映象
replace-with = 'sjtu'

# 清華大學
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"

# 中國科學技術大學
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"

# 上海交通大學
[source.sjtu]
registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index"

# rustcc 社群
[source.rustcc]
registry = "https://code.aliyun.com/rustcc/crates.io-index.git"

在設定完上述內容之後,我們可通過在終端中執行cargo -V命令來驗證安裝,如果該命令輸出了相應的版本資訊,就證明該工具已經完成了設定,並處於可用狀態了。

設定程式碼編輯器

從理論的角度上來說,要想編寫一個基於 Rust 語言開發的應用程式,通常只需要使用任意一款純文字編輯器就可以了。但在具體的專案實踐中,為了在工作過程中獲得程式碼的語法高亮與智慧補全等功能以提高編碼體驗,並能方便地使用各種強大的程式偵錯工具和版本控制工具,我們通常還是會選擇使用一款專用的程式碼編輯器來編寫程式碼。在這方面的工具選擇上,筆者個人會推薦讀者使用 Visual Studio Code 這一款編輯器(以下簡稱 VSCode 編輯器)。下面就讓我們來簡單介紹一下這款編輯器的安裝方法,以及如何將其打造成一款用於編寫 Rust 應用程式的整合式開發環境吧。

VSCode 是一款微軟公司於 2015 年推出的現代化程式碼編輯器,由於它是一個基於 Node.js 這個跨平臺執行時環境的開源專案,所以在 Windows、macOS 以及各種類 UNIX 系統上均可使用(這也是筆者推薦這款編輯器的重要原因之一)。VSCode 編輯器的安裝非常簡單,在通過搜尋引擎找到並開啟它的官方下載頁面之後,就會看到如下圖所示的內容:

然後,大家需要根據自己所在的作業系統來下載相應的安裝包。待下載完成之後,我們就可以開啟安裝包來啟動它的圖形化安裝嚮導了。在安裝的開始階段,安裝嚮導會要求使用者設定一些選項,例如選擇程式的安裝目錄,是否新增相應的環境變數(如果讀者想從命令列終端中啟動 VSCode 編輯器,就需要啟用這個選項)等,大多數時候只需採用預設選項,直接一路點選「Next」就可以完成安裝了。

接下來,我們的任務就是要將其打造成可用於開發 Rust 應用的整合式開發環境。眾所周知,VSCode 編輯器的最強大之處在於它有一個非常完善的外掛生態系統,我們可以通過安裝外掛的方式將其打造成面向不同程式語言與開發框架的整合式開發環境。在 VSCode 編輯器中安裝外掛的方式非常簡單,只需要開啟該編輯器的主介面,然後在其左側縱向排列的圖示按鈕中找到「擴充套件」按鈕並單擊它,或直接在鍵盤上敲擊快捷鍵「Ctrl + Shift + X」,就會看到如下圖所示的外掛安裝介面:

根據開發 Rust 應用的需要,筆者在這裡會推薦安裝以下 VSCode 外掛(但並不侷限於這些外掛)。

  • rust syntax:該外掛可以為 Rust 程式碼檔案提供語法高亮功能。
  • crates:該外掛可以幫助開發者分析當前專案的依賴是否是最新的版本。
  • rust test lens:該外掛可以用於快速執行某個 Rust 測試。
  • rust-analyzer:該外掛會實時編譯和分析我們編寫的 Rust 程式碼,提示程式碼中的錯誤,並對型別進行標註。
  • better toml:由於 Rust 開發使用 toml 格式的檔案來充當專案組態檔,所以我們通常會需要一個能方便用於編輯該格式檔案的外掛。
  • Tabnine AI Autocomplete:這是一款基於 AI 的自動程式碼補全外掛,可以幫助開發者們更快地撰寫程式碼。
  • GitLens:該外掛用於檢視開發者們在Git版本控制系統中的提交記錄。
  • vscode-icons:該外掛用於為不同型別的檔案加上不同的圖示,以方便檔案管理。
  • Path Intellisense:該外掛用於在程式碼中指定檔案路徑時執行自動補全功能。

當然,VSCode 編輯器的外掛浩若繁星,讀者也可以根據自己的喜好來安裝其他功能類似的外掛,只要這些外掛後面的專案實踐需求即可。除此之外,Atom 與 Submit Text 這兩款編輯器也與 VSCode 編輯器有著類似的外掛生態系統和使用方式,如果讀者喜歡的話,也可以使用它們來打造屬於自己的專案開發工具,方法是大同小異的。

基本程式設計步驟

自《The C Programming Language》這本在程式設計領域的經典教材問世以來(中文版譯為《C 程式設計語言》),嘗試在命令列終端環境中輸出「Hello World」字樣的資訊已經成為了開發者們學習一門新的程式語言,或者驗證該語言的開發環境是否已經完成設定的第一個演示程式。因為這樣做不僅可以先讓初學者對要學習的程式語言及其執行程式的方式有一個整體的印象,同時也為接下來的程式語言學習提供一個切入點。所以,現在就讓我們閒話少說,正式從零開始構建一個 Rust 版的 Hello World 程式吧!

Hello World

在 Rust 官方提供的開發工具集中,Cargo 是初學者首先要學會使用的命令列工具,因為它除了是用於解決專案中依賴關係的包管理器之外,同時也是在我們開發專案時要使用的自動化構建工具(其功能類似於 Node.js 執行時環境中的 NPM)。下面,就讓我們先來演示一下如何使用該工具建立並執行一個 Hello World 專案,以便初步瞭解開發一個 Rust 專案所要執行的基本步驟,具體如下。

  1. 使用 Bash 或 Powershell 之類的命令列終端進入到之前在本篇筆記開頭建立好的,計劃用於存放程式碼範例的examples目錄中,並執行cargo new hello_world命令來建立新專案。如果一切順利,該命令的執行效果在 Powershell 中應該如下圖所示。

    上述操作會在新建專案的根目錄下自動生成一個名為Cargo.toml的檔案,它是當前專案的組態檔,我們可以用它來設定專案的名稱、版本號以及依賴包等選項,其初始內容如下。

    [package]
    name = "hello_world"
    version = "0.1.0"
    edition = "2021"
    
    # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    
    [dependencies]
    

    除專案組態檔之外,上述操作也在專案根目錄下自動生成了一個名叫src的目錄,這是是用於存放專案原始碼的地方,例如該目錄下的main.rs檔案,就是當前這個 Hello World 程式的原始碼,其初始內容如下。

    //  main() 是程式的入口函數
    fn main() {
        // 呼叫一個用於在終端中輸出一行字串的宏
        println!("Hello, world!");
    }
    

    這段原始碼也是我們在執行cargo new命令時自動生成的,其中使用fn關鍵字定義了一個main()函數,和大多數程式語言一樣,main()函數是命令列介面程式的入口函數,即所有命令列介面程式的執行過程通常都會以該函數為起點。在main()函數中,我們呼叫了一個用於在命令列終端環境中輸出一行字串資訊的宏:println!()。在 Rust 語言中,宏的名字都以!符號結尾,這種機制供了類似函數的功能,但沒有執行時開銷,與之相對的,需要付出一些程式碼在編譯期展開的開銷)。

  2. 如果想要執行這個 Hello World 程式,我們就只需要在該專案的根目錄下執行cargo run命令即可,該命令在 Powershell 中的執行效果如下圖所示。

正如讀者所見,上面這個 Hello World 專案的建立、編譯以及演示操作都是使用 Cargo 這個自動化工具來完成的。下面,讓我們來逐一介紹一下上述操作過程中所執行的命令。

  • 首先是cargo new [專案名稱]命令。該命令的職責是根據其預設的專案模板建立一個指定名稱為[專案名稱]的專案。通常情況下,該專案中會包含有一個檔名為Cargo.toml的專案組態檔,和一個用於存放專案原始碼的src目錄(該目錄下會有一個名為main.rs的原始碼檔案,其中包含的是 Hello World 程式的簡單實現)。

  • 接下來是cargo run命令。該命令其實執行的是兩個操作:第一個操作是將原始碼編譯成可執行的二進位制檔案;第二個操作是在當前終端環境中執行該二進位制檔案,後者會輸出「Hello, World」字樣的資訊。如果讀者只想執行原始碼的編譯操作,也可以改為在相同的目錄下執行cargo build命令,該命令的執行效果應如下圖所示。

需要特別說明的是,這裡執行的Cargo build命令也只是一個 Rust 專案的自動化構建命令,它其實也執行了兩個操作:第一個操作是呼叫一個名為rustc的命令來執行編譯動作,後者才是真正用於編譯 Rust 原始碼的編譯器。第二個操作是將編譯的結果儲存到專案根目錄下的target/debug目錄中(如果該目錄不存在,就先自動建立它)。在預設情況下,該命令編譯的結果是帶有偵錯資訊的 Debug 版本,如果讀者想生成體量更小的、不帶偵錯資訊的 Release 版本,就需要在執行cargo runcargo build命令時加上--release引數,其在 Powershell 中的執行效果應如下圖所示。

這一回,編譯結果將會被儲存在專案根目錄下的target/release目錄中。當然了,如果我們不想使用自動化工具來編譯原始碼,並自行決定編譯結果的儲存目錄,也可以直接在命令列終端中手動呼叫rustc編譯器,例如像這樣。

在上述演示中,我們直接使用了rustc命令編譯了src/main.rs檔案中的程式碼,並使用-o引數為其指定了編譯結果的檔名及其儲存位置,其效果與cargo build命令基本系統,只是擁有了更大的自由度。雖然這兩種方式都可以用於編譯 Rust 專案,但通常情況下,我們會更推薦使用 Cargo 這種自動化工具來完成相關的任務。讀者也將會在後面的演示專案中看到,後者在面對多檔案編譯的任務時能更方便地處理專案中複雜的依賴關係。

輸入與輸出

現在,讀者已經初步接觸了 Rust 專案的基本構建方法。接下來,為了鞏固目前所學,我們可以嘗試著將上面這個由 Cargo 自動生成的 Hello World 程式改造得更復雜一些,使它能根據使用者輸入的名字來輸出相應的資訊,其具體步驟如下。

  1. 使用 Bash 或 Powershell 之類的命令列終端進入到之前準備好的、用於存放程式碼範例的examples目錄中,並執行cargo new input_output命令來建立新專案。

  2. 使用 VSCode 編輯器開啟剛剛新建的專案,並在該專案的src目錄下找到名為main.rs的原始碼檔案,然後將其中的程式碼修改如下。

    // 引入標準庫中的 I/O 模組
    use std::io::{self, Write};
    
    // 將相關操作封裝成函數
    fn say_hello () {
        // 定義一個可變數,用於接受使用者的輸入
        let mut input = String::new();
        // 使用 print! 這個宏輸出不帶換行符的字串資訊
        print!("Please give me a name: ");
        // 重新整理標準輸出的快取,以確保上面的字串已經被輸出到終端中
        io::stdout().flush().expect("output error!");
        // 使用 match 關鍵字匹配使用者執行輸入之後的狀態
        //  io::stdin().read_line() 方法會從標準輸入中獲取到使用者的輸入
        match io::stdin().read_line(&mut input) {
            Ok(n) => { // 如果輸入成功,執行如下程式碼
                println!("{} bytes read", n);
                // 定義一個不可變數,用於儲存被處理後的資訊
                // input.trim() 方法用於去除掉輸入資訊中的換行符
                let name = input.trim();
                println!("Hello, world! \nMy name is {}.", name);
            }
            Err(_) => { // 如果輸入失敗,執行如下程式碼
                println!("Input Error!");
            }
        }
    }
    
    fn main() {
        // 呼叫自定義函數
        say_hello();   
    }
    
  3. 使用命令列終端進入到專案的根目錄下並執行cargo run命令,然後在程式完成編譯並執行之後,根據其輸出的提示輸入一個名字並按確認鍵即可,該操作過程在 Powershell 中的演示效果如下圖所示。

下面,讓我們來詳細講解一下main.rs檔案中的原始碼,看看這一版的 Hello World 程式相較於之前做了哪些改動,具體如下。

  1. 我們使用use關鍵字將 Rust 語言標準庫中的std::io模組中定義的函數引入到了當前模組所在的名稱空間中,以便可以呼叫stdout()stdin()這兩個函數來實現在命令列介面中的輸入/輸出(即I/O)。

  2. 我們用fn關鍵字自定義了一個名為say_hello()的函數來專門執行與 Hello World 相關的操作,並在main()函數中呼叫它。這也是函數作為基本程式設計單元的功能:封裝一組相關的操作以便可重複呼叫。

  3. 在 Rust 語言中,我們使用let關鍵字來定義變數,在預設情況下變數是唯讀的。如果我們想定義一個可變的變數,就需要在let關鍵字後面再加上一個mut關鍵字。例如在上述程式碼中,input是一個可變的字串型別的變數,它的值最初是一個空字串,後來被賦予了使用者輸入的資訊,而name則是一個唯讀變數,它在定義時被賦予了input.trim()這個呼叫返回的資訊之後,值就不能再被修改了。關於這種不可變性的設計目的,我們將會留到[[Rust 學習筆記:語法學習篇]]中再展開具體討論。

  4. 在 Rust 語言中,標準庫與第三方庫都是以 crate 為單位存在,這是一種二進位制格式的語言擴充套件包。在上述程式碼中,我們使用的stdoutstdin物件及其方法都是屬於std::io這個 crate,它屬於 Rust 語言標準庫的一部分。關於在使用 Rust 編寫應用程式時如何使用標準庫以及第三方擴充套件庫的問題,我們將會留到《[[Rust 學習筆記:擴充套件庫應用篇]]》中再展開具體討論。

  5. 在 Rust 語言中,許多物件的方法都會返回一個Result型別的列舉物件,該物件往往會與關鍵字match 搭配使用,組成一個用於處理執行時錯誤的模式匹配表示式,該表示式可以方便地根據Result物件的列舉值來執行不同的程式碼。Result物件的列舉值有OkErr兩種,其中的Ok值表示操作成功,內部包含成功時產生的值。而Err值則意味著操作失敗,並且包含失敗的前因後果。

    除此之外,具體到io::Result型別的範例,它們通常都擁有expect()方法。當io::Result範例的值為Err時,expect()方法就會主動終止程式的執行,並顯示被當做引數傳遞給該方法的字串資訊。例如在上述程式碼中,如果io::stdout().flush()方法返回的是Err,則可能是發生了底層作業系統錯誤,expect()方法就會在命令列終端中輸出「output error!」這個字串資訊之後終止程式的執行。而如果io::stdout().flush()方法返回的是Okexpect()方法就會獲取到操作成功之後的值並原樣返回。在上述程式碼中,這個值是使用者輸入的名字資訊。

    關於Result型別的具體使用以及與之相關的錯誤處理機制,我們也會留到[[Rust 學習筆記:語法學習篇]]中再展開具體討論。

現在,筆者已經對 Rust 語言的基本使用步驟做了一次鳥瞰性的概述,只要讀者能掌握上述內容所涉及的知識點,就可以開始具體的語法學習了。接下來,筆者將會繼續用《[[Rust 學習筆記:語法學習篇]]》、《[[Rust 學習筆記:擴充套件庫應用篇]]》、《[[Rust 學習筆記:專案實踐篇]]》三篇筆記來記錄後續的學習心得,敬請期待。


待深入