Rust語言是什麼? 漫畫圖解讓你一目了然

Mozilla Taiwan
17 min readFeb 12, 2019

--

作者:Lin Clark Rust 團隊

Rust 2018 的第一版已於去年 12 月底正式釋出。這一版的 Rust 特別強調提高 Rust 開發的生產力。

不過,除了可以提高效率以外,我們很難具體形容 Rust 2018 的內涵。

有人認為它是新版本的程式語言。雖然這麼說沒錯,但也不盡然對。「不盡然」的原因是,如果它是新版的語言,其管理版本的方法有別於其他語言。

以絕大多數的程式語言來說,只要一推出新版本,所有新功能和特點都會加到新版本中,而舊版則不會有新功能。

Rust 的版本更新模式則不同。這是因為受到 Rust 語言演進模式的影響,所有的新功能幾乎都與原 Rust 版本完全相容,而不需重大的更新。這也意味著不把新功能限制在 Rust 2018 的程式碼中,而新版本的編譯器 (complier) 將繼續支援「Rust 2015 模式」,也就是你預設使用的模式。

不過,為了推動 Rust 語言的進展,有時候必須增加新東西 (如:新的語法),而這些新語法可能在現有程式碼庫中造成問題。

我們可以 async/await 為例。Rust 本來沒有 async 和 await 的概念,但事實證明這些基本語法元件很管用,可以簡化非同步代碼的編寫過程,也不會使程式碼變得太笨重。

為了添加此功能,我們得將 async 和 await 增為關鍵字,但同時也得避免導致舊碼失效……可能會把 async 或 await 當成變數名稱 (variable names) 的程式碼。

因此,我們已將 async 和 await 兩個關鍵字加入 Rust 2018,雖然 async/await 的功能尚未實現,但已預留其關鍵字。接下來三年開發工作 (如:增加新關鍵字) 所需的重大變更都將於 Rust 1.31 中一步到位。

雖然 Rust 2018 有重大變更,但這不代表你的程式碼會有問題。就算你的程式碼有 async 或 await 的變數名,它仍將繼續執行編譯。除非你另做指示,否則編譯器仍會認定你要它照過去同樣的方式來編譯程式碼。

不過,當你想使用這些重大的新功能時,你可選擇轉換成 Rust 2018 模式。你只需執行 cargo fix,它便會告訴你是否需更新程式碼才能使用新功能。它也幾乎可自動完成必要的更新,然後你可將 edition=2018 加到 Cargo.toml 來設置和使用新的功能。

Cargo.toml 的版本說明 (edition specifier) 無法套用到你的整個專案……它不適用於管理你的相依關係 (dependencies) 上,因為它在設計上便只限於那一個程式碼包 (crate)。也就是說,你能夠一次擁有具備 Rust 2015 和 Rust 2018 的crate graphs。

因為這個緣故,雖然 Rust 2018 已經上線,它看起來還是和 Rust 2015 差不多。大多數的更新都將同時於 Rust 2018 與 Rust 2015 上線。只有少數需重大變更的功能不支持 Rust 2015。

Rust 2018 帶來的不僅是針對核心語言的更新,絕非如此。

Rust 2018 努力提高 Rust 開發人員的生產力。工作效率的紅利大多來自於核心語言以外的面向 (如:工具的改善),以及針對特定使用場景 (use case) 的改進,使 Rust 成為實踐此些場景之最具效率的語言。

因此,你可將 Rust 2018 看成 Cargo.toml 中的 specifier,可用以啟用少數需重大更新的功能……

或者,你可把它看成一個特定的時間點,每當你需要更高效能、更輕量的實作或更高的可靠性時,Rust 2018 就能在許多使用案例中成為極具效率的語言。

在我們眼中,這還不是最重要的,所以,我們先來看看核心語言以外的所有事情,然後再來深入探討核心語言。

針對特定場景的 Rust

抽象的程式語言本身不具任何生產力,必須放在使用場景中才能創造出生產力。因此,Mozilla 的 Rust 團隊知道,我們不僅得把 Rust 變成更好的語言或工具,還需在一些領域中讓 Rust 變得更易於使用。

在某些情況下,這意味我們必須為全新的生態系打造一套全新的工具。

在別的情況下,這代表我們必須改善生態系中既有內容並整理好文檔,以利開發者快速採用。

Rust 團隊組成四個工作小組,分頭進行以下領域的開發:

  • WebAssembly
  • 嵌入式應用
  • 網路
  • 命令行工具

WebAssembly

工作小組必須為 WebAssembly 建立一整套全新的工具。

就在去年,WebAssembly 實踐了讓像 Rust 這樣的語言在 Web 上運行的理想。從那以後,Rust 迅速成為整合現有 Web 應用的最佳語言

基於兩個因素,Rust 非常適合用於 Web 開發:

  1. Cargo 的crate生態系運作模式和大多數 Web 應用開發者習慣的方式一樣 — — 把一群小模組湊在一起組成更大的應用程式。這代表你可在需要的地方輕鬆地使用 Rust。
  2. Rust 只需要很小的記憶體空間,且無需 runtime 即可運作,因此,你不必加入大量的程式碼。假使你需要一個能處理大量運算工作的小模組,只要加入幾行 Rust 便可加快執行速度。

透過 web-sys 和 js-sys 的 crates,你可很簡單地從 Rust 碼呼叫包括 fetch 或 appendChild 在內的 Web API。還有,wasm-bindgen 可輕鬆支援 WebAssembly 原生環境不支持的高階資料類型。

編寫好 Rust WebAssembly 模組後,Rust 還有工具能快速將其嵌入至 Web 應用的其他部分中。你可使用 wasm-pack 自動執行這些工具,如果你想要的話,還可將新模組推送到 npm。

你可參考Rust 和 WebAssembly 手冊,自己動手做看看。

下一步是什麼?

Rust 2018 釋出後,工作小組正積極擬定下一步的工作事項。他們將與社群合作,鎖定下一個重點工作。

嵌入式開發

在嵌入式開發方面,工作小組的目標是要保持既有功能的穩定。

理論上,對於嵌入式開發來說,Rust 已經是很好用的語言了。它為嵌入式開發人員提供極其欠缺的現代開發工具及非常方便的高階語言功能,而且還不會過度耗用資源。因此,Rust 似乎是非常適合嵌入式開發的語言。

然而,在實務上,Rust 還是有些問題。那時,必要的功能尚待釋出穩定版。另外,標準的函式庫 (library) 也必須稍做改變才能用於嵌入式設備上。也就是說,開發者必須自行編譯其 Rust 核心 crate (用於每個 Rust 應用中來作為 Rust 的基本構件 — — 包括內建值與原始值)。

由於這兩個因素,開發人員過去得仰賴 Rust 的每日搶先版 (Nightly) 來作業,但因為沒有針對微控制器的目標進行自動化測試,每日搶先版常無法正常執行這些目標。

為了解決這個難題,工作小組需確保穩定版本包含必要功能,以及在持續整合 (CI) 系統中納入微控制器目標的測試,以避免在桌面端元件增加功能造成嵌入式組件失效的問題。

經過這些改進,Rust 的嵌入式開發已從高風險的狀態朝向高生產力的方向前進。

你可參考嵌入式 Rust 開發手冊,親自嘗試看看。

下一步是什麼?

隨著過去一年的推展,Rust 已是極適合支援大量應用於嵌入式裝置之 ARM Cortex-M 微處理器核心的語言。不過,還是有很多用於嵌入式設備的架構未取得完善的支援。Rust 仍需繼續往前擴展,以為其他架構提供同樣層級的支援。

網路

針對網路,工作小組需為 Rust 語言構建核心的抽象概念 — — async/await。藉此,開發者才能在非同步程式碼中使用 Rust。

網路方面的工作常需要等待。例如,你可能要等請求的回應。如果你的程式碼是同步的,在請求抵達前,執行該程式碼的 CPU 核心就得暫停工作而無法處理其他任務。但若程式碼為非同步,等待回應的函式可暫停工作,CPU 核心則可繼續處理其他函式。

Rust 2015 已開始支援非同步 Rust 程式碼的編譯。這帶來許多優點。就大的層面而言,例如包括伺服器應用在內的服務,程式碼便能讓每台伺服器處理更多連線 (connections);至於小一點的事情,如在微型單執行緒 CPU 上運作的嵌入應用來說,非同步程式碼也可更充分運用單執行緒的資源。

然而,這些優點也帶來一個大缺點 — — 那些程式碼無法使用借用檢查器 (borrow checker),而開發者也得編寫出不符語言習慣 (以及易於混淆) 的 Rust。這就是 async/await 派上用場的地方。它能為編譯器提供所需的訊息,使其在各非同步函式呼叫之間調用 borrow check。

1.31 版已納入 async/await 的關鍵字,但目前尚未得到實作支持。不過,大部分的工作均已完成,此功能應可在下一版本中釋出。

下一步是什麼?

除了為網路應用保障低階開發的效率之外,Rust 還可為較高階的開發工作創造生產力。

很多伺服器常須處理同樣的任務,它們必須剖析 URLs 或執行 HTTP 任務。如果這些任務能變成元件 (變成可作為 crates 分享的通用抽象值),它們就能被輕鬆地彙整,以組成各式各樣不同的伺服器及框架。

為了推動元件開發過程,我們運用 Tide 框架打造這些元件的測試平台及使用範例。

命令行工具

在命令行工具的方面,工作小組需要將更小的低階函式庫帶向更高層次的抽象,並精進一些現有的工具。

在部分 CLI 指令碼的處理上,使用 bash 常是理所當然的選擇。例如,如果你只需要叫出別的 shell 工具及在其中傳輸資料,bash 便是最好的選擇。

不過,Rust 也非常適用於別種的 CLI 工具。譬如,若要建立類似 ripgrep 的複雜工具、或在現有函式庫的功能上外加 CLI 工具,Rust 便非常好用。

Rust 除了不需要 runtime,還支援單獨的靜態二進制檔案的編譯,所以很便於散布 (distribute)。此外,它還能提供 C 和 C++ 等其他語言所無法達成的高層次抽象;單就這一點而言,Rust CLI 開發者已能享受到高效率。

那麼,工作小組還有哪些方面可以改進?那就是更高層次的抽象化。

有了更高層次的抽象後,開發者就能更便捷地組合出隨時可上線生產的 CLI。

此些抽象的例子之一是 human panic 函式庫。若無此函式庫,CLI 程式碼一旦出錯,就可能輸出整個 back trace,而會造成終端用戶的困擾。你可以自訂錯誤的處理方法,但那又很費事。

相較之下,如果使用 human panic,輸出便將自動轉到錯誤傾印 (error dump) 檔案。用戶則將收到建議通報此問題及上傳錯誤傾印文件的訊息。

另外,工作小組還降低了 CLI 開發的技術門檻。例如,confy 函式庫可自動完成新 CLI 工具大部分的安裝流程,而只會問兩個問題:

  • 你的應用程式叫什麼名字?
  • 你想公開 (expose) 哪些配置選項 (你定義的結構可被序列化及反序列化的部分)?

答覆這兩個問題後,confy 就會幫你完成其他的工作。

下一步是什麼?

工作小組抽取出許多 CLI 中通用的不同任務,但還有更多可被抽象化的任務。接下來,工作小組將建立更多高階的函式庫,並陸續解決更多的問題。

Rust 工具

想要體驗一個語言,最好的方法是透過工具體驗。第一步便是要從你使用的編輯器開始,再延續至開發和維護的各個階段。

換言之,語言的生產力取決於高效的工具。

以下是一些隨著 Rust 2018 釋出的工具 (和針對 Rust 既有工具提供的改進)。

IDE 支援

當然,高效率的前提是要能將程式碼從腦中快速地轉到螢幕上,箇中關鍵是 IDF 的支援。為了支援 IDEs,我們需透過工具讓 IDE 知道 Rust 程式碼的含意,如:告訴 IDE 什麼樣的字串才算完成了程式碼。

在 Rust 2018 的改進過程中,社群特別鎖定 IDEs 所需的功能。透過 Rust Language Server 和 IntelliJ Rust,現在許多 IDEs 已享有完備的 Rust 支援。

加快編譯過程

以編譯來說,速度越快就越有生產力,所以,我們把編譯器變快了。

以前編譯 Rust crate 時,編譯器會重複編譯該 crate 中每一個單獨的文件。現在,因為有了增量編譯 (incremental compilation) 功能,編譯器變聰明了,而只會重複編譯變更之處。與其他優化搭配下,這大大加快了 Rust 編譯器的速度。

rustfmt

高效率的另一關鍵是不必費心解決風格問題 (以及不必再為格式規則爭論不休)。

對此,rustfmt 採用預設的風格 (是經過社群達成共識的風格),自動將程式碼重新格式化。使用 rustfmt 可確保所有的 Rust 程式碼都採用同一風格,就像 C++ 採用 clang format 和 JavaScript 採用 Prettier 一樣。

Clippy

我們都有想向經驗老到的顧問諮詢的時候,以在編寫程式碼的過程中取得最佳實務的建議。這就是 Clippy 的效果 — — 它能在編碼過程中檢查程式碼,並提供寫出更符合語用習慣的程式碼的建議。

rustfix

不過,如果你手上有的是一個採用過時用語的老程式碼庫,取得提示及動手改程式碼的過程恐怕很枯燥乏味。你只希望有人能幫你更正程式碼庫的問題。

在那種情況下,rustfix 便可將整個流程自動化。它將運用來自 Clippy 等工具的 lints 和更新較老舊的程式碼,以符合 Rust 2018 語法的需要。

Rust 本身的變更

這些在生態系中的改變已在許多層面提高了生產力,但有些生產力問題只能透過語言本身的變更來解決。

就像我在一開始的說明中所提到的,針對 Rust 語言的大部分變更都與現有的 Rust 程式碼完全相容。這些變動都是 Rust 2018 的一部分。不過,因為它們不會破壞任何程式碼,所以,就算該程式碼不使用 Rust 2018,也可在所有的 Rust 程式碼中運作。

我們先來看一些已納入各版本的重大 Rust 語言功能,然後再來看 Rust 2018 獨有功能的小清單。

所有版本通用的新語言功能

下面是一些 Rust 語言現階段 (及未來) 的重大功能更新。

更精確的 borrow checking (如:NLL (非詞彙生命週期))

Rust 的一大賣點是借用檢查器 (borrow checker)。雖然借用檢查器可確保程式碼的記憶體安全性,但一直是令 Rust 新手聞之色變的大挑戰。

原因之一是新手必須學新的概念,除此以外,還有另一個更大的問題……借用檢查器有時會拒絕一些連了解那些概念的人看起來都沒問題的程式碼。

這是因為借用 (borrow) 一次的生命期 (lifetime) 應持續到其作用範圍 (scope) 結束為止,例如:持續到該變數所在的函式尾端。

也就是說,就算變數的值已經失效且不再存取該值,一直到函式結束為止,其他的變數仍將被拒絕存取。

為解決此問題,我們也把借用檢查器變聰明了。現在,它可以看到哪個變數真的不需要某個值,一發現該變數值的生命期告終,就不會攔阻其他數據的借用。

目前,此功能僅於 Rust 2018 落地,但在不久的將來便將於所有版本上線。屆時我也將撰文說明。

穩定版 Rust 引入程序式巨集 (procedural macros)

Rust 在 Rust 1.0 以前已有巨集 (macro) 的功能。不過,隨著 Rust 2018 的釋出,我們也推出一些重大改善,包括加入程序式巨集 (procedural macros)。

程序式巨集的用途類似於讓你添加自己的語法到 Rust 中。

Rust 2018 提供兩種不同程序式巨集:

「函式」類巨集

函式類的巨集提供類似正常函式呼叫的東西,但這些東西實際上會在編譯時執行。它們會接受一些程式碼和輸出不同程式碼,後者再由編譯器插入二進位檔案中。

它們存在已久,但支援的功能非常有限。你的巨集只能取得輸入碼 (input code) 並對其執行 match statement,而無法查看該輸入碼內的所有代碼 (token)。

現在,有了程序式巨集的幫忙後,你可獲得和剖析器 (parser) 一樣的輸入值,也就是代碼流 (token stream)。換句話說,你就能建立更強大的函式類巨集。

「屬性」類巨集

如果你對 JavaScript 等語言中的裝飾器 (decorator) 很熟悉的話,就會發現它們和屬性類巨集有諸多相似之處。它們讓你在 Rust 中註釋一些應被預處理及轉換為其他內容的程式碼。

derive 巨集的用途正是如此。當你把 derive 放到一個結構上,編譯器便將接收該結構 (在這之前此結構會先被剖析成一個代碼清單) 並予以處理。編譯器將從特徵 (trait) 中加入函式的基本實作。

更合乎習慣的匹配借用

這個變更很明確易懂。

以前,如果你想借點東西來執行匹配 (match) 的話,就必須加入一些奇怪的語法:

不過,現在你不需要再寫 &Some(ref s) 了,而只要寫 Some(s),Rust 就能幫你完成匹配。

Rust 2018 特有的新功能

Rust 2018 還有一些獨有的新功能。以下列出 Rust 2018 已加入的部分更新:

關鍵字

Rust 2018 中新增了一些關鍵字。

  • try 關鍵字
  • async/await 關鍵字

這些功能尚未完全實現,但其關鍵字正於 Rust 1.31 落地。也就是說,等到這幾個關鍵字背後的功能正式實現後,我們就不必再引入新的關鍵字了 (新增關鍵字會造成重大變更)。

模組系統

對於想學習 Rust 語言的開發者而言,模組系統是一大痛點,而原因也不難理解:因為以前很難推論 Rust 會選擇使用哪個模組。

為解決此問題,我們稍微調整了 Rust 中路徑的運作模式。

例如,以前,當你匯入一個 crate 後,你可在最頂層的路徑中使用它。但若將程式碼中的任何一部分移入子模組,它就將失去效用。

// 頂層模組

extern crate serde;

// 這在頂層中可順利執行

impl serde::Serialize for MyType { … }

mod foo {

// 但無法在子模組中運作

impl serde::Serialize for MyType { … }

}

另一個例子是前綴 ::,它被用來指稱 crate root 或外部 crate。以前,我們很難判斷它到底指向的是兩者中的哪一個。

因此,Rust 2018 將此區分得更明確。現在,如果你想參照 crate root,就改用前綴 crate::。除此以外,我們還推出一些有助於加強路徑明確性的改進。

如果你手上有 Rust 程式碼而希望它採用 Rust 2018 的話,那你很可能得為這些新的模組路徑來更新程式碼。不過,別擔心,你不必手動更新。只要在將新版本 specifier 加到 Cargo.toml 前先執行 cargo fix,rustfix 便將幫你處理所有的更新。

進一步了解

請參考 Rust 2018 版本指南,全盤掌握新版本的所有內容。

原文連結

歡迎加入 Mozilla Taiwan Line@ 好友,隨時與我們保持互動!

--

--

Mozilla Taiwan

我們是Mozilla 美商謀智台灣分公司,由非營利組織 Mozilla 基金會所擁有,在台灣為自由開放的網路未來而努力。