iT邦幫忙

2024 iThome 鐵人賽

DAY 18
0

WebAssembly (以下簡稱Wasm) 是一種高效能的二進位格式,可以在現代瀏覽器中運行近乎原生速度的程式碼。這讓 Wasm 成為在網頁上執行高效能應用的理想技術。Rust 是支援 Wasm 編譯的語言之一,其強大的編譯器能夠將 Rust 程式碼轉換為 Wasm,這使得 Rust 開發者能夠輕鬆將高效能應用程式部署到網頁上。

在這篇文章中,我們將介紹 Wasm 與 Rust 的整合,並通過建立一個簡單的 Wasm 應用來演示如何將 Rust 程式碼嵌入到網頁中運行。

一、WebAssembly 的革命性潛力

Wasm 是一種經過精心設計的低階字節碼格式,旨在讓程式能夠在瀏覽器內以接近原生應用的速度運行。它不僅僅是一種效能提升工具,更是開啟了全新網頁應用時代的技術基礎。Wasn 的出現改變了我們對於瀏覽器內部效能的認知,尤其對於那些曾經需要依賴桌面應用來達到高效能需求的領域,如 3D 遊戲、圖像處理、科學計算等。

Wasm 的關鍵特點包括:

  1. 跨平台無縫運行:Wasm 無需依賴特定平台,可以在所有現代瀏覽器上運行,從而消除了過去需要為不同設備編寫不同版本程式碼的麻煩。這種跨平台的能力,不僅提升了開發效率,還為用戶提供了統一、流暢的使用體驗。

  2. 接近原生速度的效能:Wasm 在瀏覽器中以接近原生的速度運行,這對於如高效能遊戲、視訊編碼、數據可視化等應用至關重要。以前這些應用往往只能在桌面或專用硬體上實現,但有了 Wasm,這些高效能需求的應用如今也能在瀏覽器中輕鬆執行,並且無需為不同的硬體平台特別優化。

  3. 語言無關性:Wasm 的架構允許多種語言編譯為 Wasm 格式,如 C、C++、Rust 等。這意味著開發者可以使用最適合解決問題的語言,而不必拘泥於某個語言的限制。Rust 作為一個現代化系統級語言,因其記憶體存安全、高效能特性,與 Wasm 的結合變得特別強大,使得 Rust 成為開發高效能網頁應用的理想選擇。

透過 Wasm,網頁應用的潛力不再局限於腳本語言的範疇,它為我們打開了運行複雜應用、實現真實效能需求的大門。而 Rust,作為 Wasm 生態中支援度最強的語言之一,則進一步放大了這一潛力,成為推動網頁開發革新的關鍵力量。

二、建立一個簡單的 Rust WebAssembly 應用

1. 安裝必要工具

首先,確保你已經安裝了 Wasm 的相關工具。Rust 提供了 wasm-pack 來幫助開發者將 Rust 編譯成 Wasm,並整合到 JavaScript 環境中。

可以通過以下命令來安裝 wasm-pack ,安裝可能需要等一段時間:

cargo install wasm-pack

2. 建立專案

接下來,使用 cargo 建立一個新的 Rust 專案:

cargo new --lib hello_wasm
cd hello_wasm

這會建立一個 Rust 函式庫專案,我們將在其中編寫 Wasm 相關的程式碼。

3. 設定 Rust 專案

在專案的 Cargo.toml 檔案中,加入 Wasm 相關的設定:

[package]
name = "hello_wasm"
version = "0.1.0"
edition = "2021"

[dependencies]
wasm-bindgen = "0.2"

[lib]
crate-type = ["cdylib"]
  • wasm-bindgen 是一個可以讓 Rust 程式與 JavaScript 互操作的工具,我們將使用它來將 Rust 函數導出給 JavaScript 調用。
  • [lib] crate-type = ["cdylib"] 這個設定,簡單來說,我們是在告訴 Rust:

「嘿,這個專案最後要輸出一個可以被其他程式(例如 JavaScript)使用的東西,所以請把它編譯成一個特別格式,讓它可以和其他程式合作。」

cdylib 這種類型的庫使用 C 語言的 ABI(應用二進制介面),而 C ABI 是大多數語言都支持的標準。因此,當我們想要讓 Rust 與 JavaScript(透過 Wasm)或其他語言互動時,cdylib 能讓 Rust 程式輸出一個其他語言能理解的格式。

這個設定幫助我們把 Rust 程式編譯成一種格式,讓瀏覽器中的 JavaScript 透過 Wasm 能夠讀取和執行 Rust 的功能。

4. 撰寫 Rust 程式碼

src/lib.rs 中撰寫以下簡單的 Rust 函數:

use wasm_bindgen::prelude::*;

// 將 Rust 函數導出給 JavaScript 調用
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

use wasm_bindgen::prelude::*; 這段程式碼是在引入 wasm-bindgen 庫中的預設功能(即 prelude),讓你可以使用 wasm-bindgen 提供的各種功能來與 JavaScript 互動。

這段程式碼定義了一個 greet 函數,接受一個字串參數,並返回一個問候訊息。通過 #[wasm_bindgen] 巨集,讓這個函數可以被 JavaScript 調用。

5. 編譯 WebAssembly

使用 wasm-pack 將 Rust 編譯為 Wasm:

wasm-pack build --target web

這會生成一個 pkg 資料夾,其中包含了 .wasm 檔案以及相應的 JavaScript 包裝程式碼,可以直接在網頁中使用。

三、整合到網頁中

1. 建立 HTML 檔案

在專案目錄下建立一個簡單的 index.html 文件,並載入剛才編譯好的 Wasm 檔案:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rust WebAssembly</title>
</head>
<body>
    <h1>Rust 與 WebAssembly 範例</h1>
    <input type="text" id="name" placeholder="輸入你的名字" />
    <button id="greet">打招呼</button>
    <p id="output"></p>

    <script type="module">
        import init, { greet } from './pkg/hello_wasm.js';

        async function run() {
            await init();

            const button = document.getElementById("greet");
            const input = document.getElementById("name");
            const output = document.getElementById("output");

            button.addEventListener("click", () => {
                const name = input.value;
                output.textContent = greet(name);
            });
        }

        run();
    </script>
</body>
</html>

你的 index.html 應該建立在專案的根目錄下,與 Cargo.toml 位於同一層級。這樣可以確保 Wasm 編譯後的檔案能夠正確被載入並與網頁整合。

通常來說,專案的目錄結構會像這樣:

my_project/
│
├── src/
│   └── lib.rs          # Rust 程式碼
│
├── Cargo.toml          # 專案的 Cargo 設定檔
├── index.html          # 你的 HTML 檔案
└── pkg/                # WebAssembly 編譯後生成的 JavaScript 與 Wasm 檔案
    └── hello_wasm.js   # 編譯後的 WebAssembly 模組

當你執行 wasm-pack build 來編譯 Rust 到 Wasm 後,生成的 Wasm 檔案(如 .wasm 和對應的 JavaScript 模組 hello_wasm.js)會存放在 pkg/ 資料夾內。這時你的 index.html 就可以從 pkg/ 資料夾中載入這些檔案來實現互操作。

這個 HTML 檔案提供了一個簡單的使用者介面,允許使用者輸入名字,然後點擊按鈕顯示問候訊息。JavaScript 會載入 Wasm 模組,並調用 Rust 定義的 greet 函數。

2. 使用本地伺服器運行網頁

miniserve 是一個簡單且強大的靜態文件伺服器工具,可以用來運行本地的 Wasm 項目。以下是如何使用 miniserve 運行你的專案:

安裝 miniserve

首先,需要安裝 miniserve,你可以通過以下命令來安裝它:

cargo install miniserve

啟動伺服器

安裝完成後,在專案根目錄執行以下命令來啟動伺服器:

miniserve . --index index.html

這個命令將把當前目錄設為伺服器的根目錄,並設置 index.html 作為默認首頁。miniserve 會在 http://localhost:8080 提供網頁。

訪問網頁

打開瀏覽器,訪問 http://localhost:8000,你將看到網頁正常運行,並可以輸入名字並使用 Rust 生成問候訊息。

https://ithelp.ithome.com.tw/upload/images/20241001/20121176DbAtIAAMOr.png


四、實際應用範例:計算階乘

我們可以進一步擴展這個應用,來展示 Rust 和 Wasm 的性能。我們將實作一個計算階乘的函數,並在網頁中運行。

lib.rs 當中新增階層運算函數

use wasm_bindgen::prelude::*;

// 將 Rust 函數導出給 JavaScript 調用
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

#[wasm_bindgen]
pub fn factorial(n: u32) -> u32 {
    (1..=n).product()
}

這段程式碼定義了一個計算階乘的 Rust 函數。

更新 index.html

在 HTML 中新增一個輸入框和按鈕,讓使用者輸入數字並計算其階乘。

<input type="number" id="number" placeholder="輸入一個數字" />
<button id="calculate">計算階乘</button>
<p id="result"></p>

<script type="module">
    import init, { factorial } from './pkg/hello_wasm.js';

    async function run() {
        await init();

        const calculateButton = document.getElementById("calculate");
        const numberInput = document.getElementById("number");
        const resultOutput = document.getElementById("result");

        calculateButton.addEventListener("click", () => {
            const num = parseInt(numberInput.value, 10);
            resultOutput.textContent = `階乘結果:${factorial(num)}`;
        });
    }

    run();
</script>

記得再次執行 wasm-pack build --target web 指令,讓函數被打包到 pkg 當中,接著我們可以透過 miniserve . --index index.htmlindex.html 開啟到 http://localhost:8080 當中,並且由瀏覽器互動。

在這個範例中,當使用者輸入一個數字並點擊按鈕後,Rust 的 factorial 函數會被調用並返回計算結果,如下圖所示:

https://ithelp.ithome.com.tw/upload/images/20241001/2012117656y2BzFFL1.png

當然,階層運算結果如果發生 溢位 了,輸出結果就會為0,因為我們所設定的資料類別為 u32,所以如果將資料類別改為 u128 則就能儲存更大的數字位數。


五、關於 WebAssembly 與 Rust 的整合

我們已經展示了如何將 Rust 程式編譯為 Wasm,並在網頁中調用它的基本流程。接下來,我們將更深入探討 Rust 與 Wasm 的整合如何在實際應用中發揮其強大功能。

1. 精確的記憶體管理與安全性

Rust 在記憶體管理上有著無可比擬的優勢,它以「所有權」系統來避免許多常見的記憶體錯誤,例如空指標引用或記憶體洩漏。而這種特性在 Wasm 中同樣適用,能夠保證在網頁應用中執行的程式碼擁有極高的記憶體安全性,減少崩潰的風險。這對於那些需要高可靠性的網頁應用(如金融應用或醫療數據處理)至關重要。

2. 複雜計算的效能優化

Wasm 的一個核心優勢是能夠處理計算密集型任務。Rust 作為系統級語言,擅長進行精細控制,並且能夠直接管理硬體資源。結合 Wasm,可以讓那些需要高效運算的任務(例如 3D 渲染、加密運算、科學計算)以接近原生的速度在瀏覽器中執行。這意味著,我們可以在網頁上實現以前只能在桌面應用中達到的效能。

3. 與 JavaScript 的高效互操作

儘管 Wasm 可以處理複雜的計算,JavaScript 仍然是瀏覽器中的主導語言。通過 wasm-bindgen,Rust 能夠與 JavaScript 進行高效互操作,這允許開發者根據具體需求,將高效能的部分用 Rust 編寫成 Wasm,而將其他邏輯用 JavaScript 實現。這種分工可以提升整體應用的可維護性和擴展性。

實際應用範例:

例如,在一個需要即時影像處理的網頁應用中,影像處理的核心邏輯可以使用 Rust 寫成 Wasm 模組,負責繁重的運算。JavaScript 則負責處理用戶界面、交互以及 API 調用等較輕量的任務。這樣的組合可以大大提升效能,並保持良好的開發體驗。

4. 跨平台與可移植性

Wasm 不僅能在瀏覽器中運行,還可以在其他環境如伺服器或 IoT 設備中使用。Rust 的跨平台特性與 Wasm 的無縫整合,意味著開發者可以使用同一套程式碼,來部署在多個不同平台上運行,減少了重寫程式碼的需求並提升了開發效率。

5. 未來的潛力:Server-side WebAssembly

除了在瀏覽器中使用 Wasm,Rust 還有潛力成為 伺服器端 Wasm 的一部分。未來,伺服器端應用可以利用 Wasm 將一些邏輯部署在伺服器上運行,並透過 Wasm 確保跨平台和高效能運行,這種模式有望改變雲端計算的現有格局。

六、總結

在這篇文章中,我們介紹了如何將 Rust 程式碼編譯為 Wasm,並將其嵌入到網頁中運行。我們通過簡單的問候範例,學習了 Rust 與 Wasm 的整合,並且進一步展示了計算階乘的應用場景。

Rust 與 Wasm 的結合,讓開發者能夠在網頁中運行高效能的程式,同時保持記憶體安全和程式的穩定性。這對於構建計算密集型的應用程式(如遊戲、視訊處理)具有極大優勢。

下一篇,我們將進一步展示 Rust 使用 Wasm 在網頁上運作時,如何能夠更進一步結合 React 完成高效的網頁開發。


上一篇
[Day 17] 建立你的第一個 Rust CLI 應用程式
下一篇
[Day 19] Rust 與 React 結合:建立簡單的 Web 應用
系列文
從 Python 開發者的角度學習 Rust —— 從語法基礎到實戰應用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言