Skip to Content

我眼中的 Rust 2020:生態工具發展

最近越來越多人想要學 Rust,也有一些朋友來諮詢 Rust 相關的生態,這裡稍微囉嗦一下我眼中的 Rust 2020 吧。

如果沒有拖稿的話,文章應該會分三篇,表列如下:

本篇將會介紹 「Rust 生態工具發展」

註零:本文會有大量連結,歡迎點進去。

註一:本文是於 2020-09-19 和朋友討論的文字整理,所以會和「好讀文章」有點差距,請見諒。

註二:本文和 Rust 官方的 Call for blogs 2020 無關,主要是回顧 2020 這年 Rust 在我眼中如何存在。

與其他語言的 Interpolation

我先從「與其他語言的 Interpolation」來說:

Rust 其實一開始的定位就是 system programming language,沒有 GC,沒有 runtime(或 minimal runtime),所以跟 C 溝通非常容易,社群也有很多工具可以自動產生 header 給 C call 或是從 C lib 建立給 Rust 用的 FFI 的工具,很多社群選擇用 Rust 包裹一層 C code 就是因為 Rust 相對有許多安全檢查(預設 move semantic + object lifetime 檢查),讓 C 的 library 可稍微 robust 一點,至少 caller 這端不用自己 free 有 boundary check 不會 buffer overflow。

對 C++ 來說也是一樣,有生成 binding 的工具,最近 Google 實驗在 Chromium 導入 Rust,所以開發了自動生成 binding 的工具,雖然 Rust 和 C++ 有諸多雷同之處(Rust for C++ programmers 參考手冊),但因為 C++ 過於複雜,所以 binding 工具有些爭議(有人怕會錯)。

其他 GC 類語言的 binding 像 Python bindingRuby bindingNode.js bindingN-API binding)、 Java JNI binding and another 都在蓬勃發展,我知道前四個都用用在大公司正式環境,尤其是 Python binding 看起來做得很好,基本上串接其他語言不會很痛。

另外,敝司之前為了降低 sha256 hash 過慢,曾經用另一個快速的 cryptographic hash BLAKE3 就是用 Rust 寫然後 Node.js binding 的 node module。

有多少函式庫,都在發展什麼輪子

再來是「有多少函式庫,都在發展什麼輪子」。

Rust 的函式庫走的是中心化 npm 風格,叫做 crates.io,上面充滿各式各樣有用和無用的函式庫,不過基本上你想做的東西一切都有,例如:

  • Event loop、async runtime: 這塊目前算很成熟,官方語法只定義了 future 的介面,其他 runtime reactor task 都讓社群實作,所以出現有 libuv 等級的 tokio,第二大的 async-std,也有半官方工具包 futures,我自己覺得比 Python async 更成熟(感覺啦)
  • Concurrency tools: 這塊非常多,有 parallism 和 channel 之類的工具,例如 rayon 的目標就是 data parallism,而 crossbeam 則是 std 的 concurrency 強化版。
  • Web framework: 有點像 Python 一樣多頭馬車,actixrocketwarp 這三個自己比較推薦,但都沒有 Django 和 Rails 這麼功能豐富而肥大,比較像 falcon 那樣簡單精巧。 HTTP/gRPC:hyper 基本上是 de-facto HTTP lib,我貢獻過一些,品質不錯,最近 cURL 的作者想要底層可以抽換 hyper 這樣;gPRC 大概有三家 tonictarpc(Google 內部使用)、和 grpc-rs(TiDB 使用),tonic 的 benchmark 印象中和 Google 自己的 gRPC C library 比起來成績平分秋色。
  • 搜尋引擎: 搜尋引擎和 web framework 一樣不少選項,有最近拿到融資的開源版 Algolia MeiliSearch,Elasticsearch 替代方案的 SonicToshi,有加上 Raft 搜尋引擎 Bayard,還有作為 Lucene 潛在對手的 Tantivy
  • 遊戲引擎: Rust 就是在蠶食鯨吞 C++ 的疆域,所以有許多優秀的 engine,上次分享過的 bevy、看起來很強的 Amethyst,還有 Piston,不過 game engine 的市場大多被 Unreal 和 Unity 吃下來,所以自研引擎和遊戲目前都是小眾(但「戰神」系列的 CTO 之前說新遊戲要改用 Rust 做不知道現在怎樣了)。
  • 物理引擎: 講完遊戲就要講它底層的物理引擎,最知名的應該是 rapiernphysics 繼任者),當然物理引擎少不了 algebra library(還有其他小的但我忘了)
  • parser 和程式語言: parser 不少,例如 peg parser 的 pest,或是另一個 nom parser 和 haskell parsec inspired 的 combine。用 Rust 寫出來的程式語言更多,我這邊懶得列 XDDD
  • serialize/deserialize: 基本上這塊被 serde 一統江湖(python 超快 orjson 就是 serde binding),支援的格式很多,和 haskell 很像,只要在 struct 上面加 annotation 就會自動產生序列化反序列化的實作。而像是 Google 的 flatbuffer 也有 Rust 支援
  • Graphics: 基本上 gfx-rs 整個 org 都在做 vulkan metal 還是什麼顯卡 render pipeline 的串接,剩下還有一些小的 lyon 像,Embark Studio 也在嘗試把 Rust 作為 GPU 界一等公民語言
  • GUI library: 不免俗 GTK 有 binding(GNOME 官方),然後最新版的 GNOME 已經有 built-in app 是用 Rust 寫的。剩下還有什麼 azuldruid 等很多,不過都沒有 Qt 成熟,也不像 Electron 方便,可以參考 areweguiyet.com
  • 資料庫: TiKV 是 TiDB 底層的 kv storage,原生支援分散式、Sled 是小型類似 SQLite 的 KV storage 適合用在 IoT(ORM 我就不想講了)
  • 作業系統: 這個主題比較有趣,有 AWS 開源給 container VM 的 bottlerocket,還有 AWS lambda serverless 底層的 firecracker、有 Google 下一代作業系統 Fuchsia(核心部分一半 Rust 一半 C++),還有從 kernel 開始自幹重寫的作業系統 Redox OS(真的可以在裸機上面安裝) 安全元件:openssl 的替代品 rustls 最近經過一些第三方審計,可以免除 OpenSSL heart bleeding 的問題;Sequoia-PGP 是維護 GnuPG 團隊其中三個人自己出來幹的 PGP(GPG)實作。
  • 嵌入式系統: 這塊我比較不熟(文章都跳過),但 Rust 官方有個 dedicated 團隊在搞這個,基本上常見的開發版都能編譯,而且 Rust struct 編譯出來最後會是 zero cost abstraction,所以寫嵌入式變得更語義化,而不是一直在搞 bit。Google 和一堆知名大學也合力開發 Tock 安全嵌入式系統
  • 視訊、音訊: 這邊我也比較不熟,但 Mozilla 有小組在搞下一代影音格式 rav1e,應該是 AV1 官方之外最快最 robust 的選擇;image-rs 也是非常實用的各種圖片格式轉換的函式庫
  • 前端框架與 WASM: 因為 Rust 原生支援 compile 成 WebAssembly,所以就有了好幾套前端框架,最大一套叫做 yew,核心維護者現在在台灣(是外國人)。當然 WebAssembly 目前就是 Rust 的天下,例如 WASI 這個跑在非瀏覽器的標準就是用 Rust PoC。其他 Python(RustPython) 或 Ruby(Artichoke) 的 in browser interpreter 都是借助 Rust combine to WASM 很方便才辦得到。
  • Scientific computing: 這塊死了又活活了又死,有很多 dataframe 或類似 pandas Weld,不過看起來還是 Python 比較有優勢。
  • C library binding: 太多,都可以透過工具搞出來,基本上有 C 就可以幾乎自動搞一個零成本 Rust binding 沒問題。

Cross-Compilation、Distribution 工具支援程度

接下來是「Cross-Compilation、Distribution 工具支援程度」。

  • Rust 基本上 cross compilation 非常方便,除了 rustc 支援,也 cross 工具省去部分設定的麻煩,這個列出 rustc tier 1 2 3 支援列表,加上 rust compiler 現在目前就只有 rustc 一個實作,所以沒有什麼 gcc llvm 支不支援哪個 feature 的問題。
  • 針對不同平台的 conditional compilation 也非常方便,在你要的 function struct 或是各種 item 上加 #[cfg(windows)] #[cfg(not(unix))] 之類的就可以 ,所以真的實作不同可以直接分開不用 runtime 判斷。
  • Rust 編譯出來的東西就是一個 binary 或 .so .dylib .dll ,如果不是 no-std(給嵌入式的特殊 cfg),除非你有另外使用其他特殊函式庫,基本上只 depends on libc,所以完全 portable,不會有什麼要 apt-get install python3-dev 之類的。
  • 在 Linux distro 分發 Rust 的工具我知道的目前 Debian Fedora Ubuntu 已經在做,不過都是終端工具,dynamic lib 目前好像還沒有,主要是 Rust ABI 還沒有 stable 的問題
  • 一個範例是我自己的小工具,CI 就寫這樣,然後就可以在三大平台跑測試,CD 也是類似的指定 target,就可編譯出三大平台的 binary 供下載。
  • 另一個八卦是 ARM 最近給 Rust team 錢和編譯機器資源,想要提升 Rust 對 arm 的編譯支援程度達到 tier-1 等級,RFC 已經合併了

IDE 和 debugger、測試和開發工具支援程度

有關「IDE 和 debugger、測試和開發工具支援程度」:

這個你就來對時間了,如果你在 2019 年來,我會說只有 Intellij IDEA 這個 IDE 可以用,但 Rust 社群最近寫出了一個 langauge server 套件 rust-analyzer,基本上類似 reimplement rust 編譯器的部分實作,我覺得用在 VIM 搭配 YouCompleteMe 完全無違和(雖然我最近改成 Neovim built-in LSP),從 root 開 30 萬行大型專案基本上有時候一點點卡,但通常都只會進到 submodule 去修改就完全不卡。

Rust 我寫到現在很少用到 debugger,而且因為通常編譯錯誤多於 runtime error,所以其實相較之下很少用到 debugger,如果真的要用就要靠 Intellij IDEA 的 IDE 支援,或是 VS Code + rust-analyzer 下 breakpoint,這是我自己不熟從 terminal 下斷點啦,不過 Rust 最容易被誇獎的地方就是 compile error 非常明確告知是什麼原因,甚至會教你要怎麼改寫,我覺得 DX 很好。

Rust 編譯不用自己 link,類似 cmake 和 ninja 的工具叫做 cargo,其實應該算 npm + build system 集大成,所以不用記得一堆指令,寫好 Cargo.toml(Rust 的 package.json)一切搞定。測試,benchmark,產生 API doc、發佈、跑 example 都是透過 cargo 這個工具。cargo 這個東西也支援 plugin subcommand,所以除了內建 cargo build 編譯,也可以安裝社群的 cargo cache 清理快取, cargo audit 對 dependency 安全檢查,或是 cargo afl 做 fuzz testing,有夠實用的就可能會納入官方 subcommand。

另一個很實用的就是 Rust 的 docstring 是 markdown,所以非常好撰寫文件,我覺得可以用 markdown 當作 docstring 的原因是因為 compile strong type language 不需要再寫 parameter 是什麼,反正都在 function signature 上面。然後所有發佈到 crates.io 的 library,它的 API doc 都會統一在 docs.rs 上面出現,例如這個 Redis client 就是從 docstring 生成的文件。

Rust 的測試就更有趣了:

  • Unit test: 和 source code 寫在同一個檔案裡,用 #[cfg(test)] 做到 conditional compilation,只會在編譯測試時被編譯,編譯 debug/release mode 不會,這樣除了要測試的 code 和 unit test 很接近以外,也不用再考慮「這個內部函式到底要不要測試,我是不是要 public 一下測試但在註解說不要用這是內部函式」,反正在同一個檔案都可以 access
  • Integration test: 獨立在另一個資料夾,基本上就和大多數語言一樣,只能 access public interface,自己是一個獨立的 compile target 沒什麼太特別
  • Benchmark: 這算特殊的 test compile target,這裡面的測試會多跑幾次跑出統計結果這樣。
  • Doctest: 剛剛講的 docstring,如果 markdown 裡面有 rust code block,就可以透過 doctest 測試你的 rust code block 是否可以正確編譯成功,不會讓 doc outdated,這裡有一個很好玩的 feature,假如你的 doc 只希望顯示 3 行 code ,但 setup 這 3 行需要其他 10 行才能成功編譯,你可以用特殊的 # prefix 隱藏你不想要顯示的行數
  • Examples: Rust 內建 code example compile target,在跑測試也可以一併跑這些範例,讓你範例不要 outdated

Rust 官方也有一個 formatter rustfmt,是選用的(不像 go format 不過編譯就不會過….),但是很多專案都會用,可以省去很多格式上的爭論;另外也有類似 eslint pep8 這種可以移除 bad smell 讓 code 更 rusty 的 rust-clippy(clippy 就是以前 windows 迴紋針小幫手的名字 XDD),但有時候太刁鑽個人沒有特別喜歡。

Rust 官方自己有出一個類似 nvm rvm rbenv pyenv 這種工具叫做 rustup,從下載不同版本的 Rust,安裝一些 rust 工具週邊(在 Rust 叫做 component),到鎖定某個資料夾要用哪個版本編譯,編譯的 target 要是 linux-musl 還是 linux-gnu ,32 bit 還是 64 bit,都可透過 rustup 直接 override 你的 default 設定,原本測試跑在穩定版 cargo test 你想要測試 nightly 版也只要 cargo +nightly test 就可以,神之方便。

我自己覺得 2020 年是 Rust 開發體驗大躍進的一年,整個開發阻力降低非常多,很適合入場。

穩定性與 Compatibility

剩最後一點「穩定性與 Compatibility」我快速打完:

有人說 Rust 語言很新一直更版不穩定,事實上 Rust 非常嚴格遵守語義化版號 semver。從 2015 五月十五日 Rust 1.0 發佈到現在穩定版的 1.46,基本上沒有任何 breaking change。而且 Rust 版本發佈非常規率,嚴格的六週發佈一個 minor version,所以跟著官方就會一直升級上去不會有特別的問題。

Rust 團隊想要導入 breaking change 的時候怎麼辦,他們開發了一種叫做 edition 的機制,目前預計是每三年發佈一次(有點像 c++11 14 17?),這個 edition 可能會有很小的 breaking change,例如將 async 原本不是關鍵字改為關鍵字,這種情況下 Rust 官方為了不要真的引入 breaking change,用了幾個手法緩解:

  • 第一個就是 edition 是可以選擇的,如果你不想要升級到 2018 版本,那就留在 2015,所有東西都照常可以使用,只是 2018 一些新的語法糖就享受不到,但所有 compile 更新什麼的都會獲得,並不是 LTS 的概念。
  • 第二個就是 Rust 團隊為了這個 edition 專門寫了一本 Edition Guide 告訴你每個版本有什麼新功能,你需不需要,要怎麼改寫或不改寫。
  • 第三個就是直接提供 codemod 工具 cargo fix subcommand,協助你 migrate 到新版本,但你留在 2015 版,你還是可以獲得幾乎所有編譯器的新 feature 和 bugfix,不會有任何問題和差異,就是寫法舊了點繞了點。

Rust 團隊接下來可能會為了 compatibility 做的功夫是可以Cargo.toml 指定最低支援的 Rust version,這可以讓社群的函式庫作者或使用者都能明確得知他的 rustc 版本是否足夠支援使用該 library,作者也可以很放心的寫出什麼時候要 drop 哪一個版本的 rustc 支援,我個人非常其他這個功能實現,但應該要一段不少的時間。

大概以上,我還沒想到更多。