[譯] Rust 2018 已經發布……但它到底是什麼呢?

子非發表於2019-01-23

本文是與 Rust 團隊(指文中“我們”)合著。你也可在 Rust 部落格上閱讀他們的宣告

今天,Rust 2018 釋出了它的第一個版本,在這個版本中,我們專注於提高生產力,讓 Rust 開發人員儘可能高效。

時間線顯示了不同的程式,Rust 2018 和 Rust 2015,並且 beta 版上的功能指向另兩個版本。包圍時間線的是工具圖示及 4 個領域:WebAssembly,嵌入式,網路和命令列介面(CLI)。一個紅色的環包圍了所有的東西(除了 Rust 2018 字樣和 Developer Productivity 標籤)

但除了這些,很難準確解釋 Rust 2018 是什麼。

一些人認為它是一門語言的新版本,確實可以這麼認為,但其實又不完全是。我說“不完全是”是因為如果這是語言新版本,它並不像其它語言的版本更新。

在大多數其它語言中,當新版本到來時任何新功能都會新增到此版本中。之前版本並不會得到功能更新。

Rust 的版本則不同。這是因為語言的演進方式不同。幾乎所有新功能都會 100% 相容原有的 Rust。他們不需要重大的更新。這意味著不需要把他們限制在 Rust 2018 程式碼中。新版本的編譯器會繼續支援“Rust 2015 模式”,也就是你預設使用的模式。

然而因為有時候要改進語言,你必須新增新的東西(像新語法)。並且這類新語法會在老的程式碼庫下執行失敗。

async/await 舉例來說。Rust 最初並沒有 asyncawait 的概念。但是事實證明這些簡單的語法實際上非常有用。它們讓非同步程式碼易於編寫並且不會使程式碼變得笨重。

為了讓新增這個功能成為可能,我們需要把 asyncawait 新增為關鍵字。但我們必須小心不能使舊程式碼失效……程式碼可能會把 asyncawait 當做變數名使用。

所以我們把新增關鍵字作為 Rust 2018 的一部分。雖然這個功能本身還沒實現,但現在關鍵字已經被保留。接下來三年開發(例如新增關鍵字)所需的所有重大變化都是在 Rust 1.3.1 中一次性完成的。

時間線顯示 Rust 2015 連線到 Rust 2018 釋出的 1.31 版本

雖然 Rust 2018 有重大的變化,但這並不意味著你的程式碼會執行失敗。你的程式碼會繼續編譯甚至會把 asyncawait 當做變數名。除非你告訴它,不然編譯器會假設你想讓它當前以相同的方式來編譯程式碼。

不過一旦你想使用這些新的重大功能,你可以設定成 Rust 2018 模式。你僅需要執行 cargo fix,它會告訴你如果你需要更新你的程式碼來使用新功能。它幾乎可以自動地處理更改。然後你可以新增 edition=2018 到你的 Cargo.toml 來設定和使用新功能。

Cargo.toml 中的 edition 說明符不適用於你的整個專案……它不適用於你的依賴。它只限於一個包。這意味著你將能夠擁有相互依賴的 Rust 2015 和 Rust 2018 包。

[譯] Rust 2018 已經發布……但它到底是什麼呢?

因此,即使 Rust 2018 已經出現,它大致看起來與 Rust 2015 相同。大多數變化都將同時出現在 Rust 2018 和 Rust 2015 中。只有少數需要重大變化的功能不會同步支援。

[譯] Rust 2018 已經發布……但它到底是什麼呢?

Rust 2018 不僅僅是對核心語言的改變。事實上,遠非如此。

Rust 2018 致力於提高 Rust 開發人員的工作效率。許多生產力的提高來自核心語言以外的東西……比如工具。他們還專注於特定用例,並弄清楚 Rust 如何成為這些用例最有效的語言。

因此,你可以將 Rust 2018 視為 Cargo.toml 中的說明符,你可以使用它來啟用少數需要重大更改的功能……

時間線的箭頭指向幾個 Rust 2018 的功能,這些功能在 Rust 2015 中不會通過。

或者你可以把它想象成一個時刻,在許多情況下,Rust 成為你可以使用的最有效的語言之一 —— 每當你需要效能,輕巧的實現或高可靠性時。

[譯] Rust 2018 已經發布……但它到底是什麼呢?

在我們看來,這是第二點。讓我們先來看看核心語言之外的所有事情。然後我們可以深入研究核心語言本身。

針對特定用例的 Rust

抽象描述的程式語言自身並不能高效,但在一些用例中它會很有效。因此,團隊知道我們不僅需要將 Rust 作為一種語言或 Rust 工具更好,我們還需要在特定領域中使 Rust 更容易使用。

[譯] Rust 2018 已經發布……但它到底是什麼呢?

在某些情況下,這意味著要為一個全新的生態系統建立一套全新的工具。

在其他情況下,它意味著打磨已經存在於生態系統中的內容並將文件做好,以便保持生態系統的成長和正常運作。

Rust 團隊成立了專注於四個領域的工作組:

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

WebAssembly

對於 WebAssembly,工作組需要建立一整套新工具。

就在去年,WebAssembly 讓編譯像 Rust 這樣的語言在 Web 上執行成為可能。從那時起,Rust 迅速成為與現有 Web 應用程式整合的最佳語言。

Rust logo 和 JS logo 之間有一顆心

Rust 非常適合 Web 開發,原因有兩個:

  1. Cargo 包生態系統的工作方式與大多數 Web 應用程式開發人員習慣的方式相同。你將一堆小模組組合在一起構成一個更大的應用程式。這意味著在你需要的地方可以很容易地使用 Rust。
  2. Rust 實現簡單並且不需要特定的執行環境。這意味著你無需嵌入大量程式碼。如果你需要一個很小的模組進行大量繁重的計算工作,你可以引入幾行 Rust 來加快執行速度。

使用 web-sysjs-sys crates,很容易在 Rust 程式碼呼叫類似於 fetchappendChild 的 Web API 。並且 wasm-bindgen 可以輕鬆支援 WebAssembly 原生不支援的高階資料型別。

一旦編寫了 Rust WebAssembly 模組,就可以使用各種工具將其嵌入到 Web 應用程式的其餘部分中。你可以使用 wasm-pack 自動執行這些工具,並根據需要將新模組推送到 npm。

Check out the Rust and WebAssembly book to try it yourself. 建議你檢視 Rust 嵌入式書籍並親自嘗試一下。

下一步呢?

現在 Rust 2018 已經完成,工作組正在確定下一步要做什麼。他們將與社群合作,確定下一個重點領域。

嵌入式

對於嵌入式的開發,工作組需要使現存的功能穩定。

理論上,Rust 對於嵌入式開發來說已經是非常出色的語言了。它為嵌入式開發人員提供了他們非常缺乏的現代工具,以及非常方便的高階語言功能。並且不需要耗費資源。因此,Rust 似乎非常適合嵌入式開發。

然而,實際上它並不好使用。必備的功能處在不穩定階段。另外,為了在嵌入式裝置上使用,需要對標準庫做一些改變。這意味著人們必須編譯他們自己版本的 Rust 核心包(用來給每個 Rust 應用提供 Rust 的基本構建模組 —— 內部模組和原始值)

左邊:一個人騎在猛然弓背躍起的微處理器晶片上,說“Whoa, Rusty!”。右邊,一個人騎在一個馴服的微處理器晶片上說“很好 Rusty,乖乖地保持穩定”

總之,這兩件事意味著開發人員必須依賴於 Rust 的每日構建版本。並且由於沒有針對微控制器的自動化測試,每日構建版在這些目標上會經常出錯。

為了解決這個問題,工作組需要保證穩定版本有那些必要的功能。我們也必須向持續整合系統為微控制器目標新增測試。這意味著向桌面端元件新增功能不會破壞嵌入式元件的東西。

有了這些變化,Rust 的嵌入式開發將從有風險的前沿邁向高效。

建議你檢視 Rust 嵌入式書籍並親自嘗試一下。

下一步呢?

隨著近年來的推進,Rust 已經很好地支援了 ARM Cortex-M 架構的晶片處理器核心,這些核心用於大量裝置。然而,還有很多被用於嵌入式裝置的架構沒有得到很好的支援,並且沒有被很好的支援。Rust 需要被擴充套件來給這些架構以同樣水平的支援。

網路

對於網路來說,工作組需要構建核心抽象概念到語言中 —— async/await。這樣,開發者可以在非同步程式碼中使用符合語言習慣的 Rust。

對於網路任務來說,你必須要等待。例如,你可能需要等待請求的響應。如果你的程式碼是同步的,那意味著 CPU 核心正在執行的任務會停止而且不能會做其它的任何事情,直到請求進來。但如果你的程式碼是非同步的,那麼等待響應的函式會在 CPU 核心執行其它函式時掛起。

使用 Rust 2015 使非同步程式設計成為可能。並且還有很多優點。從大的方面講,像服務端應用之類的服務,你的非同步程式碼使得每個伺服器可以處理更多的連線。從小的方面講,對於那些執行在小型單核 CPU 上的嵌入式應用,非同步使你更好的利用單執行緒。

但這些優點也帶來了一個大的缺點 —— 你不能為那些程式碼使用借用檢查器,並且你將必須書寫符合語法習慣的(和別的令人迷惑的) Rust。這就是 async/await 出現的理由。它給予編譯器需要的資訊,這些資訊用來在非同步函式之間呼叫 borrow check。

async/await 關鍵字在 1.31 被引入,雖然它們目前的實現還不能向下相容。但大部分工作都已完成,並且你可以期待這個功能在將要到來的一個版本中可用。

下一步呢?

除了為網路應用程式實現高效的低層次開發之外,Rust 還可以在更高層次上實現更高效的開發。

許多伺服器需要去處理相同型別的任務。它們需要解析 URL 或者處理 HTTP 任務。如果把它們變成元件通用抽象型別,並且在包之間分享 —— 那麼就可以輕鬆地把它們組合在一起來組成各種各樣的服務和框架。

為了驅動元件開發過程,Tide 元件 為這些元件提供了測試平臺,並最終展示這些元件。

命令列工具

對於命令列工具,工作組需要給更小,更低階的庫引入高階的抽象,並且打磨已有的工具。

對於一些 CLI 指令碼,你真的很想使用 bash。例如,如果你僅僅需要喚出其他 shell 工具並在它們之間使用管道傳輸資料,bash 是最好的選擇。

不過 Rust 也非常適於其他的命令列工具。比如你正在構建一個複雜的工具像 ripgrep 或構建一個處在現有的庫功能之上的 CLI。

Rust 不需要執行時並且允許你編譯進單獨的靜態二進位制檔案,這樣利於分發。並且你能得到其它語言向 C 或 C++ 得不到的高階抽象,這些特性已經讓 Rust CLI 開發者更高效。

工作組需要做些什麼來改善它?是更高階的抽象。

有了這些高階抽象,組合成熟的 CLI 會快速並輕鬆。

這些抽象中的其中一個是 human panic 庫。沒有這個庫,如果你的 CLI 程式碼報錯,他可能會輸出整個錯誤棧。但這對於你的終端使用者沒有幫助。你可以新增自定義錯誤處理,不過那需要額外工作。

如果你用了 human panic,那麼輸出會自動地轉到錯誤轉儲檔案。而使用者會看到有幫助的訊息建議他們報告這個問題並上傳錯誤轉儲檔案。

命令列使用 hunam-panic 友好地輸出

工作組也讓 CLI 開發更容易入手。例如,confy 庫會自動處理一些新 CLI 工具的安裝事項。它只會問你兩件事:

  • 你的應用叫什麼名字?
  • 你想暴露那些配置選項(你定義的結構可以被被序列化和反序列化)?

從這個問題出發,confy 會幫你處理剩餘的事情。

下一步呢?

工作組會抽象出很多不同的任務,這些任務可以在不同的 CLI 中共用。但是還有更多可以抽象的。工作組會製作更多類似的高階庫,並隨著時間推移解決更多的 Pager cut bug

Rust 工具

工具圖示

當你體驗一門語言時,你需要通過工具來體驗。從你使用的編輯器開始,它貫穿於開發和維護的各個階段。

這意味著高效的語言依賴於高效工具。

這裡有一些工具(包含一些現有 Rust 工具的改進)會作為 Rust 2018 的一部分被引進。

IDE 支援

當然,高效的關鍵是將程式碼從頭腦中迅速傳遞到螢幕上。IDE 的支援嚴重影響這一點。為了支援 IDE,我們需要一些工具來告訴 IDE Rust 程式碼的實際含義 —— 例如,告訴 IDE 什麼字串對程式碼完成有意義。

在 Rust 2018 推送中,社群聚焦於 IDE 需要的功能。隨著 Rust Language Server 和 IntelliJ Rust 的發展,現在許多 IDE 已經能夠對 Rust 有良好的支援。

更快的編譯

更快的編譯意味著更高效。所以我們使編譯器更快。

以前,當你想編譯 Rust 包,編譯器會重複編譯每個包裡的單獨檔案。但是現在,使用增量編譯讓編譯器變得智慧並且只會重複編譯已經改變的部分。這和其它的優化一起使得 Rust 編譯器更加迅速。

rustfmt

高效也意味著不需要每天解決風格問題(再不需要去爭論程式碼風格規則)。

rustfmt 工具通過使用(已與社群達成共識的)預設程式碼風格自動格式化你的程式碼。使用 rustfmt 保證你所有的 Rust 程式碼符合相同的風格。就像 C++ 使用 clang format 和 JavaScript 使用 Prettier 那樣。

Clippy

有時候你的身旁能有個經驗豐富的顧問會非常好……給你提出一些程式碼的最佳實踐。這就是 Clippy 做的東西 —— 它會審查你的程式碼實現並告訴你怎樣讓程式碼更符合語言習慣。

rustfix

但是如果你在維護一個使用過時的語法的老舊程式碼庫,那麼只是獲取提示並自己來糾正程式碼可能會很乏味。你只是希望有人進入程式碼庫來更正這些問題。。

對於這些情況,rustfix 會自動化該過程。它會同時應用來自 Clippy 等工具的 lint 並更新舊的程式碼來匹配 Rust 2018 語法。

Rust 本身的變化

生態系統的這些變化已經帶來大量的生產力,但是一些生產力問題只能通過語言本身的變化來解決。

[譯] Rust 2018 已經發布……但它到底是什麼呢?

就像我在簡介中說過的,大部分語言層面的變更完全相容已有的 Rust 程式碼。這些變更都是 Rust 2018 變更的一部分。不過因為它們不會破壞原有任何程式碼,它們也可以在任何 Rust 程式碼中執行……甚至是沒有使用 Rust 2018 的程式碼。

讓我們看一些被加到全部版本的重大語言功能。然後我們可以看一下 Rust 2018 特色功能的小清單。

適用全部版本的語言新功能

這裡有一個重大新語言功能的小型示例,它(或將)被包含在所有的語言版本中

更精確的 borrow checking(例如:非詞法有效期)

Rust 的一大賣點就是借用檢查器。借用檢查器幫助你確保你的程式碼是記憶體安全的。但對於 Rust 開發新手來說它也一個痛點。

部分原因是需要學習新的概念。但還有一個大的原因……借用檢查器有時候會拒絕那些看起來應該工作的程式碼,甚至對於那些概念有足夠理解的人來說,他們也會遇到這種情況。

借用檢查器告訴程式設計師因為變數已經被借走了,所以不能去借這個變數

這是因為一次借用的有效期是到它的作用域結束為止 —— 例如,變數的有效期到函式結束為止。

這意味著儘管變數值的有效期已經結束並且不能訪問,別的變數仍然被拒絕直到函式結束。

為了解決這個問題,我們使得借用檢查器更加智慧。現在它可以看到實際正在使用的變數。如果它的有效期結束,那麼它不會阻塞其他使用資料的借用。

借用檢查器說:啊,我現在看得到了

不過當前這個特性只支援在 Rust 2018 中使用,而在不遠的將來,所有 Rust 版本都將可以使用。之後我很會寫更多關於這部分的內容。

穩定的 Rust 過程巨集

Rust 中的巨集已經出現在 Rust 1.0 之前。但是在 Rust 2018 中,我們對它做了一些重大的改進,比如引入過程巨集。

使用過程巨集,有點像你可以新增自己的語法到 Rust。

Rust 2018 帶來了兩種過程巨集:

類函式巨集

類函式巨集允許你擁有看起來像常規函式呼叫的東西,但這些東西實際上是在編譯期間執行的。他們接受一些程式碼並輸出不同的程式碼,然後編譯器將這些程式碼插入到二進位制檔案中。

他們已經存在了一段時間,但你能用它們做的事情有限。你的巨集只能獲取輸入程式碼並在其上執行匹配語句。它無權檢視輸入程式碼中的所有令牌。

但是使用過程巨集,你可以獲得與解析器相同的輸入 —— 令牌流。這意味著可以建立更強大的類函式巨集。

類屬性巨集

如果你熟悉 JavaScript 等語言中的裝飾器,屬性巨集和它非常相似。它們允許你在 Rust 中註解應該預處理並轉換為其他內容的程式碼。

derive 巨集就是做這種事情的東西。當你把 derive 放到一個結構上時,編譯器會把這個結構輸入(在它被解析為一個令牌列表之後)並處理它。具體來說,它將從特徵中新增函式的基本實現。

更多易於理解的借用匹配

這種變化非常直觀。

以前,如果你想借一些資源並嘗試匹配它,你不得不新增一些奇怪的語法:

舊版本的程式碼使用 &Some(ref s),新版本使用 Some(s)

但現在,你不再需要寫 &Some(ref s) 了。你可以只寫 Some(s),Rust 能清楚地找到(它們之間的)差異。

Rust 2018 特有的新功能

Rust 2018 的最小部分是它特有的功能。以下是 Rust 2018 版本解鎖的少數幾項更改。

關鍵字

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

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

這些功能尚未完全實現,但對應的關鍵字正在向 Rust 1.31 新增中。這意味著在將來,當我們實現了這些關鍵字背後的功能時,也不需要引入新的關鍵字(引入關鍵字將會是一個有破壞性的變更。

模組系統

開發人員學習 Rust 的一個痛點是模組系統。我們可以來看看為什麼(是這樣)。(我們)很難去推斷 Rust 會使用哪個模組。

為了解決這個問題,我們對 Rust 中路徑的工作方式進行了一些更改。

例如,如果你匯入了一個包,則可以在頂級路徑中使用它。但是,如果你將任何程式碼移動到子模組,那麼它將不再起作用。

// 頂層模組
extern crate serde;

// 這樣在頂層模組中可以工作
impl serde::Serialize for MyType { ... }

mod foo {
  // 但它在子模組中**不能**工作
  impl serde::Serialize for OtherType { ... }
}
複製程式碼

另一個例子是字首 ::,它被用來指代當前包的根目錄或一個外部包,這(兩種情況)可能很難被(使用者)區分。

我們已經明確了這一點。現在,如果要引用包的根路徑,則使用字首 crate::。這只是我們所做的 path clarity 改進之一。

如果你有現存的 Rust 程式碼並且希望它使用 Rust 2018,那麼你很可能需要為這些新的模組路徑更新它。但那並不意味著你需要手動更新程式碼。在將版本說明符新增到 Cargo.toml 之前執行 cargo fix ,並且執行 rustfix 會為你進行所有更改。

瞭解更多

Rust 2018 版本指南 中瞭解這個版本的更多內容

關於 Lin Clark

Lin 是 Mozilla Developer Relations 團隊的工程師。她瞭解 JavaScript、WebAssembly、Rust 和 Servo,還可以繪製程式碼漫畫(code cartoons)。

Lin Clark 的更多文章…

關於 Rust 團隊

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章