Rust 在前端領域的應用

什麼是 Rust?

Rust 語言原本是 Mozilla 員工 Graydon Hoare[1] 的私人項目,Mozilla 於 2009 年開始贊助這個項目,在 2010 年官方首次透露並於 2015 年發佈 1.0 版本。在過去的十年中,編程語言 Rust 一直是一項突破性的技術,Rust 始終站在獨特的學術研究和行業實用性結合的挑戰視角。但是如果說 Rust 的影響僅僅是技術性的,那就錯過了精髓,正如社區在 2016 年討論的系列博客一樣:"Rust 不僅是編程語言或編譯器 [2]"和"Rust 讓一切觸手可及 [3]",同年 Rust 宣佈了其官方口號:“一種讓每個人都能夠構建可靠和高效軟件的語言”

2021 年 2 月 8 日,Rust 基金會宣佈成立,其基金會董事成員有:AWS、Google、華爲、微軟、Mozilla 。Rust 基金會誕生自 Rust 核心團隊,並且得到了五位全球行業領先公司的財務承諾,這標誌着 Rust 向成熟化邁出了堅實的一步。

爲什麼是 Rust?

性能

各種編程語言內存管理的方式不同,但通常有以下兩種方式:

  1. 開發者自己分配和銷燬: 比如 C、C++ 等,這種方式相當於把所有權力開放給開發者,管理不當容易內存泄漏。

  2. 編程語言提供自動垃圾回收機制: 比如 JavaScript、Java、Python 等,這種方式會產生運行時開銷,對性能可能產生影響(注意這裏是 “可能”,沒有辦法證明性能一定比開發者自己管理要差)。

Rust 則另闢蹊徑採用所有權、借用、生命週期機制在編譯期自動插入內存釋放邏輯來實現內存管理,由於沒有了垃圾回收產生的運行時開銷,Rust 整體表現的速度驚人且內存利用率極高。

fn main() {
   let a = String::from("hello rust");
   let b = a;           // 所有權被轉移
   println!("{}" a);  // 編譯失敗!a 已經被釋放,無法再使用
}

在一項比較 REST API 性能的基準測試中(Rust 使用 Rocket[4],Node.js 使用 Restify[5]),Rust 每秒處理 72,000 個請求,而 Node.js 爲 8,000 個,空閒時使用的內存大約爲 1MB,而 Node.js 爲 19MB。在另一個測試中(Rust 使用 Nickel[6],Node.js 使用 Restana[7]),Rust 對請求的平均響應速度比 Node.js 快近 100 倍。

具體數據可以參考 Web Frameworks Benchmark[8]。

可靠性

Rust 豐富的類型系統和所有權模型保證了內存安全和線程安全,讓您在編譯期就能夠消除各種各樣的錯誤,關於 “內存安全” 和“線程安全”的解釋如下:

內存安全: 在具有內存安全性的編程語言中,所有內存訪問都是明確定義的,通常內存不安全的情況包含:空指針、野指針、懸空指針、使用未初始化的指針、非法釋放、緩衝區溢出、執行非法函數指針、數據競爭等。

據說微軟 70% 的漏洞是內存安全問題

線程安全: 線程安全是程序設計中的術語,指某個函數、函數庫在多線程環境中被調用時,能夠正確地處理多個線程之間的共享變量,使程序功能正確完成。

生產力

Rust 擁有出色的文檔、友好的編譯器和清晰的錯誤提示信息, 還集成了一流的包管理器和構建工具, 智能地自動補全和類型檢驗的多編輯器支持, 以及自動格式化代碼等等。

Rust 在前端構建工具中的應用

Deno

Github((80.9K star):https://github.com/denoland/deno[9]

Deno 是一個簡單、現代且安全的 JavaScript 和 TypeScript 運行時,它使用 V8 並基於 Rust 構建。Deno 是由 Node.JS 之父 Ryan Dahl[10] 創建,在 2018 JS Conf Berlin 上借演講《Design Mistakes in Node》[11] 首次對外公開。

Deno 誕生之初就是爲了解決 Node 不安全和糟糕包的管理等老生常談的問題,其中不安全時常令 Node.JS 開發者感到頭疼和憤怒,近期也剛剛發生 node-ipc 事件(node-ipc 在所有用戶的桌面上都會創建一個文件來宣傳作者的政治觀點),影響到了衆多開源項目包括 Vue CLI 等。

因此 Deno 很自然地擁有以下特性:

  1. 默認安全,除非特別啓用它,否則使用 Deno 運行的程序沒有文件、網絡或環境訪問權限

    deno run --allow-read mod.ts
  2. 開箱即用地支持 TypeScript

  3. 僅編譯單個可執行文件

  4. 擁抱 Web 生態標準,內置了 fetch、localStorage、location 等 API

    localStorage.setItem("myDemo""Deno App"); // 擁有 10M 的持久化存儲限制
  5. 內置依賴檢查器  deno info 和代碼格式化工具 deno fmt

  6. 有一組經過審查的標準模塊,可以與 Deno 一起使用:deno.land/std[12]

目前已經有衆多公司正在積極探索 Deno,包括 Amazon、Github、IBM、Vercel、Tencent、Microsoft 等頭部技術公司。

SWC

Github(21K star):https://github.com/swc-project/swc[13]

SWC 是一個可擴展的基於 Rust 的前端構建工具,目前核心功能相當於 Babel,包含以下這些模塊:

ZAJbGu

官方提供的基準測試數據如下:

Uw6JFP

SWC 在單線程上比 Babel 快 20 倍,在四核上快 70 倍。

目前 SWC 已經被 Next.js、Parcel 和 Deno 等工具以及 Vercel、字節跳動、騰訊、Shopify 等公司廣泛使用。

Parcel

Github(40k star):https://github.com/parcel-bundler/parcel[14]

支持以 HTML 作爲入口的零配置構建工具,Parcel 支持多種開箱即用的語言和文件類型,從 HTML、CSS 和 JavaScript 等 Web 技術到圖像、字體、視頻等資產。當您使用默認不包含的文件類型時,Parcel 將自動爲您安裝所有必要的插件和開發依賴項。Parcel 的 JavaScript 編譯器和源映射是建立在 SWC[15] 編譯器之上的,在 SWC 之上,Parcel 實現了依賴項收集、捆綁、搖樹優化、熱重載等。

目前 Parcel 已經被廣泛應用在微軟、Atlassian、SourceGraph 等公司。

Rome

Github(17.2 star):https://github.com/rome/tools[16]

Rome[17] 是 Babel 作者做的基於 Node.js 的前端構建全家桶,包含但不限於 JavaScript、TypeScript、JSON、HTML、Markdown 和 CSS,在 2021 年 9 月 21 日 宣佈計劃使用 Rust 重構。

其它工具(WIP)

dprint[18]:使用 Rust 編寫,比 Prettier 快 30x 倍

postcss-rs[19]:使用 Rust 編寫,比 Postcss 快 20x 倍

Rust 在桌面應用開發中的應用

Tauri

Github(34.7 star):https://github.com/tauri-apps/tauri[20]

在很長一段時間裏,包括現在,Electron[21] 都是最流行的跨平臺桌面應用開發框架,目前在 Github 上有 101K 個 star,它允許你使用純粹的前端技術(HTML、CSS、JS、Node.JS)來構建桌面應用,不過它也有兩個比較明顯的缺陷被人詬病:包體積太大和內存佔用高,而造成這兩個問題的根本原因是 Electron 是基於 Chromium 和 Node.JS 構建的。

Tauri 是 Electron 的代替品,現在 Tauri 試圖去除 Chromium 轉而使用 Rust 去和系統內置 Webview 進行綁定,簡單來說就是在 Electon 時代你的應用永遠使用 Chromium 內核,而在 Tauri 時代,你的應用在 Windows 上使用 Edge/Webview2,在 macOS 上使用 WebKit,在 Linux 上使用 WebKitGTK。基於 Rust 和 Webview 的好處很明顯:包體積極小且內存佔用極低。以下是官方提供的數據:

FyYDVp

看起來還不錯的樣子,不過別忘了如果你使用 Tauri 開發的話,後端(Electron 中叫主進程)目前只能使用 Rust,這將帶來不小的學習成本,除此之外 Tauri 還有很長的路需要走,不過也算文藝復興式的創新。

Rust 在 WebAssembly 中的應用

WebAssembly 是一種新的編碼方式,具有緊湊的二進制格式,可以在現代的網絡瀏覽器中以接近原生的性能運行,目前 Rust 和 WebAssembly 結合有兩大主要用例:

Yew

Github(Github 20k star):https://github.com/yewstack/yew[22]

Yew 是一個設計先進的 Rust 框架,目的是使用 WebAssembly[23] 來創建多線程的前端應用,它有幾個特點:

一個簡單的 Yew 應用代碼如下所示:

use yew::prelude::*;

enum Msg {
    AddOne,
}

struct Model {
    valuei64,
}

impl Component for Model {
    type Message = Msg;
    type Properties = ();

    fn create(_ctx&Context<Self>) -> Self {
        Self {
            value0,
        }
    }

    fn update(&mut self, _ctx&Context<Self>, msgSelf::Message) -> bool {
        match msg {
            Msg::AddOne => {
                self.value += 1;
                // the value has changed so we need to
                // re-render for it to appear on the page
                true
            }
        }
    }

    fn view(&self, ctx&Context<Self>) -> Html {
        // This gives us a component's "`Scope`" which allows us to send messages, etc to the component.
        let link = ctx.link();
        html! {
            <div>
                <button onclick={link.callback(|_| Msg::AddOne)}>{ "+1" }</button>
                <p>{ self.value }</p>
            </div>
        }
    }
}

fn main() {
    yew::start_app::<Model>();
}

wasm-bindgen

Github(5.0k star):https://github.com/rustwasm/wasm-bindgen[26]

目前 WebAssembly 類型系統還很小,只有四種數字類型,如果要使用複雜類型(例如字符串、對象、數組、結構體),需要花點心思:

但是每次轉換它們(序列化爲線性內存,並提供它們所在位置的引用)是一項枯燥的工作並且容易出錯,幸運的是,Rust world 想出了 wasm-bindgen 來促進 WebAssembly 模塊和 JavaScript 之間的高級交互,其使用方式也非常簡單:

  1. 創建一個 Rust 項目

    $ cargo new --lib hello_world
    Created library `hello_world` package
  2. 打開 Cargo.toml文件並添加 wasm-bindgen依賴項

    [package]
    name = "hello_world"
    version = "0.1.0"
    authors = ["Sendil Kumar <sendilkumarn@live.com>"]
    edition = "2018"
        
    [lib]
    crate-type = ["cdylib"]
        
    [dependencies]
    wasm-bindgen = "0.2.56"
  3. 打開src/lib.rs文件並將內容替換爲以下內容

    use wasm_bindgen::prelude::*;
        
    #[wasm_bindgen]
    pub fn hello_world() -> String {
      "Hello World".to_string()
    }
    
  4. 編譯成 wasm 模塊

    cargo build --target=wasm32-unknown-unknown
  5. 安裝 wasm-bindgen-cli,並將 wasm 文件轉換成 JavaScript 文件

      cargo install wasm-bindgen-cli
      wasm-bindgen target/wasm32-unknown-unknown/debug/hello_world.wasm --out-dir .
    # ls -lrta
    # 76330 hello_world_bg.wasm
    # 1218 hello_world.js
    #  109 hello_world.d.ts
    #  190 hello_world_bg.d.ts
  6. 然後你就可以使用 hello_world.js 文件了,它可以幫你加載 wasm 文件。

wasm-pack

Github(4.1k star):https://github.com/rustwasm/wasm-pack[27]

這是一個可以直接將你的 Rust 代碼打包成 npm 包的工具,用法十分簡單,只有 4 個命令:

值得注意的是,WebAssembly 目前還並不是提高 Web 應用性能的萬金油,就目前來說,在 WebAssembly 中使用 DOM API 仍然比從 JavaScript 中調用要慢。但只是暫時性問題的,WebAssembly Interface Types[28] 計劃將解決這個問題。如果你想要了解更多關於這方面的信息,可以查看 Mozilla 的這篇文章 [29] 。

Rust 和 Node 的綁定

NAPI-RS

Github(2.2k star):https://github.com/napi-rs/napi-rs[30]

NAPI-RS 是一個用於在 Rust 中構建預編譯的 Node.js 插件的框架,SWC 便基於此庫。

詳細信息可以看《用 Rust 和 N-API 開發高性能 Node.js 擴展》[31] 這篇文章。

寫在最後

隨着前端開發複雜度的不斷上升,配套工具鏈的效率將不容被忽視,受限於 Node.js 語言本身的效率問題,近幾年將會有更多工具會被 Rust 重寫,效率有望數倍乃至數十倍的提升,另外,Rust 和 WebAssembly 的結合也令人感到激動,但就目前來看離大規模上生產還有相當一段路需要走。

無論如何,對前端開發者而言現在是學習 Rust 的最佳時機。

參考資料

[1] Graydon Hoare: https://github.com/graydon

[2] Rust 不僅是編程語言或編譯器: https://graydon2.dreamwidth.org/247406.html

[3] Rust 讓一切觸手可及: https://www.thefeedbackloop.xyz/safety-is-rusts-fireflower/

[4] Rocket: https://rocket.rs/

[5] Restify: http://restify.com/

[6] Nickel: https://github.com/nickel-org/nickel.rs

[7] Restana: https://github.com/BackendStack21/restana

[8] Web Frameworks Benchmark: https://web-frameworks-benchmark.netlify.app/compare?f=express,rocket

[9] https://github.com/denoland/deno: https://github.com/denoland/deno

[10] Ryan Dahl: https://github.com/ry

[11] 《Design Mistakes in Node》: https://liguo.run/posts/node-mistakes

[12] deno.land/std: https://deno.land/std

[13] https://github.com/swc-project/swc: https://github.com/swc-project/swc

[14] https://github.com/parcel-bundler/parcel: https://github.com/swc-project/swc

[15] SWC: https://swc.rs/

[16] https://github.com/rome/tools: https://github.com/rome/tools

[17] Rome: https://rome.tools/blog/2020/08/08/introducing-rome

[18] dprint: https://github.com/devongovett/dprint-node

[19] postcss-rs: https://github.com/postcss-rs/postcss-rs

[20] https://github.com/tauri-apps/tauri: https://github.com/tauri-apps/tauri

[21] Electron: https://www.electronjs.org/

[22] https://github.com/yewstack/yew: https://github.com/yewstack/yew

[23] WebAssembly: https://webassembly.org/

[24] React: https://reactjs.org/

[25] Elm: https://elm-lang.org/

[26] https://github.com/rustwasm/wasm-bindgen: https://github.com/rustwasm/wasm-bindgen

[27] https://github.com/rustwasm/wasm-pack: https://github.com/rustwasm/wasm-pack

[28] WebAssembly Interface Types: https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md

[29] 這篇文章: https://hacks.mozilla.org/2019/08/webassembly-interface-types/

[30] https://github.com/napi-rs/napi-rs: https://github.com/napi-rs/napi-rs

[31] 《用 Rust 和 N-API 開發高性能 Node.js 擴展》: https://zhuanlan.zhihu.com/p/234914336

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/v2N08wGoqrevDWlKsnNHGw