Rust 與 WebAssembly 的結合為網頁應用程式開發帶來了新的可能性,允許開發者利用 Rust 的效能和安全性優勢構建高效率的網頁應用。本文示範的登入應用程式,即是一個典型的應用案例。透過 wasm-pack 工具,我們可以輕鬆地將 Rust 程式碼編譯成 Wasm 模組,並在 JavaScript 中呼叫。這個流程不僅簡化了開發流程,也提升了網頁應用程式的效能。文章中詳細介紹瞭如何設定開發環境、編譯 Rust 程式碼、建立網頁介面,以及整合 Rust 和 JavaScript 程式碼,讓讀者可以逐步實作一個功能完整的登入應用程式。

第19章 在網頁瀏覽器中執行Rust程式

簡介

本章將探討如何使用WebAssembly(Wasm)在網頁瀏覽器中執行Rust程式碼。我們將瞭解Wasm的基本原理及其與Rust的整合,為開發高效能和高安全性的網頁應用程式奠定基礎。在本章中,我們將介紹如何設定開發環境,利用wasm-pack和npm等工具來構建、測試和佈署Rust生成的Wasm程式碼。透過實際範例和實作練習,我們將建立從簡單的Hello-Web專案到更複雜的使用者認證系統等網頁應用程式,展示Rust和JavaScript的無縫整合,以提供強大且高效的網頁體驗。透過這次探索,讀者將獲得寶貴的見解,瞭解如何利用Rust和Wasm的結合力量來構建高效能的網頁應用程式,突破傳統網頁開發的界限。

結構

本章涵蓋以下主題:

  • WebAssembly簡介
  • 準備開發環境
  • 建立Hello-Web應用程式
  • 使用者認證網頁應用程式

目標

在本章結束時,您將具備在JavaScript程式碼中運用Rust的能力和效率,從而釋放實作接近原生效能的潛力。您將學會如何將Rust生成的Wasm二進位制檔案納入您的網頁專案中,有效地組織和利用Rust函式在您的網頁應用程式中。

WebAssembly

Wasm(通常縮寫為wasm)是一種二進位制指令格式,作為高階程式語言的可移植編譯目標。它旨在使網頁瀏覽器能夠以接近原生的速度執行,提供比傳統JavaScript更高效的替代方案,用於某些任務。本文將探討Wasm的基本原理,探討其目的、與各種程式語言的互操作性,以及其提供的效能優勢。

內容解密:

Wasm是一種二進位制格式,能夠使開發者使用多種程式語言編寫程式碼,並在網頁瀏覽器中以高效的方式執行。這使得Wasm成為開發高效能網頁應用程式的一個重要工具。Wasm的主要特點包括:

  • 可移植性:Wasm是一種平台無關的二進位制格式,可以在任何支援Wasm的環境中執行,無需重新編譯。
  • 高效能:Wasm旨在提供接近原生的執行速度,使其成為需要高效能的網頁應用程式的理想選擇。
  • 安全性:Wasm在沙箱環境中執行,提供了強大的安全隔離,確保了網頁應用程式的安全性。
// 示例:一個簡單的Rust函式,將兩個數字相加
fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    let result = add(5, 7);
    println!("The result is: {}", result);
}

內容解密:

上述範例展示了一個簡單的Rust函式add,它接受兩個i32型別的引數並傳回它們的和。這個函式可以被編譯成Wasm二進位制檔案,並在網頁應用程式中使用。

  • fn add(a: i32, b: i32) -> i32:定義了一個名為add的函式,它接受兩個i32型別的引數ab,並傳回一個i32型別的結果。
  • a + b:計算ab的和,並將結果傳回。
  • let result = add(5, 7);:呼叫add函式,將5和7作為引數傳遞,並將結果儲存在變數result中。
  • println!:列印結果到控制檯。
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Rust WebAssembly 登入應用程式開發

package "Rust 記憶體管理" {
    package "所有權系統" {
        component [Owner] as owner
        component [Borrower &T] as borrow
        component [Mutable &mut T] as mutborrow
    }

    package "生命週期" {
        component [Lifetime 'a] as lifetime
        component [Static 'static] as static_lt
    }

    package "智慧指標" {
        component [Box<T>] as box
        component [Rc<T>] as rc
        component [Arc<T>] as arc
        component [RefCell<T>] as refcell
    }
}

package "記憶體區域" {
    component [Stack] as stack
    component [Heap] as heap
}

owner --> borrow : 不可變借用
owner --> mutborrow : 可變借用
owner --> lifetime : 生命週期標註
box --> heap : 堆積分配
rc --> heap : 引用計數
arc --> heap : 原子引用計數
stack --> owner : 棧上分配

note right of owner
  每個值只有一個所有者
  所有者離開作用域時值被釋放
end note

@enduml

圖表翻譯: 此圖示展示了從編譯Rust程式碼到在網頁應用程式中執行Wasm模組的流程。首先,Rust程式碼被編譯成Wasm二進位制檔案。然後,這個二進位制檔案被納入網頁應用程式中,並在瀏覽器中執行。最後,Wasm模組完成其任務,流程結束。

透過本章的學習,您將能夠掌握如何在網頁瀏覽器中執行Rust程式碼,利用Wasm的高效能和安全性優勢,為您的網頁應用程式帶來新的可能性。

瞭解WebAssembly(Wasm)

Wasm是一種底層的位元組碼,可以在虛擬機器上執行,讓開發者能夠直接在網頁瀏覽器中執行用Rust、C和C++等語言編寫的程式碼。它的主要目標是為網頁帶來高效能的應用程式,實作超越JavaScript所能達到的效率。本章節將探討Wasm在網頁開發領域中的角色,概述其主要功能和應用案例。

Rust與WebAssembly

Rust因其對記憶體安全和零成本抽象的關注,特別適合用於Wasm開發。讓我們透過以下範例來理解為什麼Rust是Wasm開發的一個有吸引力的選擇,探討諸如所有權和借用等有助於建立高效和安全的Wasm模組的特性。

Rust程式碼範例:所有權系統

fn main() {
    let data = String::from("Hello, WebAssembly!");
    process_data(data);
    // 'data'的所有權已經轉移給process_data
    // 'data'在此處不再可存取
}

fn process_data(data: String) {
    // 處理data
    println!("{}", data);
}

上述範例展示了Rust的所有權系統,其中data字串的所有權被轉移給process_data函式。這種所有權模型與Wasm的記憶體管理需求相吻合,提供了一種安全和高效的資料處理方式。

設定開發環境

在深入Wasm開發之前,適當地設定開發環境至關重要。本文將指導您完成從安裝Rust和組態Wasm工具鏈到設定開發工具以實作順暢工作流程的步驟。

安裝Rust

要開始使用Rust進行Wasm開發,第一步是使用rustup(官方Rust工具鏈安裝程式)安裝Rust程式語言。如果尚未安裝rustup,可以使用以下命令安裝:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

接著,將Rust新增到系統PATH,以確保Rust編譯器(rustc)和套件管理器(cargo)可從命令列存取。

source $HOME/.cargo/env

檢查Rust安裝版本:

rustc --version
cargo --version

您應該會看到類別似於圖19.1的輸出。

設定WebAssembly工具鏈

安裝Rust後,下一步是設定Wasm工具鏈,特別是使用wasm-pack。wasm-pack簡化了Rust和Wasm的工作流程,處理諸如構建、測試和發布Wasm crate等任務。安裝wasm-pack:

cargo install wasm-pack

或者,可以使用以下命令:

curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

檢查wasm-pack安裝版本:

wasm-pack --version

安裝後,wasm-pack成為管理Rust中Wasm專案的寶貴工具,簡化了開發工作流程。

建立Hello-Web應用程式

準備好環境後,讓我們建立一個簡單的hello-web專案。稱之為hello-wasm:

  1. 使用cargo-generate,我們可以根據預定義範本建立新的Rust專案:
cargo generate --git https://github.com/rustwasm/wasm-pack-template

執行上述命令將提示您輸入所需的專案名稱,如圖19.2所示。 2. 指定專案名稱為hello-wasm,該工具將根據預定義範本生成專案及其相關檔案,如下圖所示:

圖表翻譯:此圖示顯示了使用cargo-generate建立新專案的流程

  1. 進入hello-wasm專案後,其結構如圖19.4所示。
  2. 在Cargo.toml檔案中,您將找到對cargo(Rust的套件管理器和構建工具)至關重要的依賴項和元資料規範。檢查Cargo.toml檔案的內容時,您將觀察到它內在地包含了一個wasm-bindgen依賴項,如下所示:
[dependencies]
wasm-bindgen = "0.2.63"
  1. 在src/lib.rs檔案中,wasm-bindgen被用來建立與JavaScript的通訊。此檔案匯入了window.alert JavaScript函式並匯出了greet Rust函式。greet函式,如下面的程式碼片段所示,觸發了一個帶有歡迎訊息的警示:
#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet() {
    alert("Hello, hello-wasm!");
}

程式碼解密:

  • #[wasm_bindgen]屬性用於指示wasm-bindgen生成必要的膠水程式碼,以便在JavaScript和Rust之間進行函式呼叫。
  • extern "C"區塊宣告了一個外部函式介面,用於與JavaScript進行互動。
  • alert函式是一個JavaScript函式,用於顯示一個包含指定訊息的對話方塊。
  • greet函式是一個Rust函式,它呼叫alert函式來顯示歡迎訊息。
  1. 接著,我們使用wasm-pack build命令編譯hello-wasm專案,如下圖所示:

圖表翻譯:此圖示顯示了使用wasm-pack build編譯hello-wasm專案的流程

使用Rust和WebAssembly建立使用者登入Web應用程式

本章節將介紹如何使用Rust和WebAssembly技術建立一個簡單的使用者登入Web應用程式。首先,我們將使用wasm-pack-template建立一個新的Rust專案。

建立wasm-login專案

  1. 使用cargo generate指令建立新的Rust專案:
cargo generate --git https://github.com/rustwasm/wasm-pack-template
  1. 指定專案名稱為wasm-login並進入該目錄:
cd wasm-login
  1. 使用wasm-pack build指令編譯專案:
wasm-pack build
  1. 初始化新的npm專案:
npm init wasm-app www
  1. 進入www目錄:
cd www

設定專案相依性

  1. www/package.json檔案中,將devDependencies更新為:
"devDependencies": {
  "wasm-login": "file:../pkg",
  ...
}
  1. 更新www/index.js檔案,將hello-wasm-pack替換為wasm-login
import * as wasm from "wasm-login";
  1. 安裝專案相依性:
npm install
  1. 啟動本地伺服器:
npm run start

建立登入介面

  1. 修改www/index.html檔案,建立登入表單:
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Login Application</title>
</head>
<body>
  <h2 id="login_heading">Login Page</h2>
  <div class="login_container" id="login_container">
    <form id="login_form" method="get" action="login.php">
      <label id="username_label">
        <b>User Name</b>
      </label>
      <input type="text" name="username" id="username">
      <br><br>
      <label id="password_label">
        <b>Password</b>
      </label>
      <input type="password" name="password" id="password">
      <br><br>
      <input type="button" name="login_button" id="login_button" value="Login">
      <input type="button" name="reset_button" id="reset_button" value="Reset">
      <br>
    </form>
  </div>
  <script src="./bootstrap.js"></script>
</body>
</html>

內容解密:

上述HTML程式碼建立了一個簡單的登入表單,包含使用者名稱和密碼欄位,以及登入和重設按鈕。表單的action屬性設定為login.php,但目前尚未實作後端邏輯。

顯示登入介面

  1. 瀏覽http://localhost:8080/,將顯示登入介面和一個提示視窗。
  2. 移除www/index.js檔案中的wasm.greet()函式呼叫,以避擴音示視窗出現在UI上方。

建立 WebAssembly 登入應用程式的完整

在前面的章節中,我們已經完成了基本的 wasm-login 應用程式的建置。現在,我們將進一步美化我們的登入介面並新增互動功能。

美化登入介面

在移除預設的警告視窗後,我們的介面看起來有些粗糙。為了提升使用者經驗,我們需要對介面進行美化。首先,我們建立一個名為 www/style.css 的 CSS 檔案,並加入以下樣式:

.div_login {
  width: 382px;
  overflow: hidden;
  margin: auto;
  margin-top: 20px;
  margin-right: 0;
  margin-bottom: 0;
  margin-left: 450px;
  padding: 80px;
  background-color: #e4ebe9;
  border-radius: 15px;
}

h2 {
  text-align: center;
  padding: 20px;
}

#Uname,
#Pass {
  width: 300px;
  height: 30px;
  border-radius: 3px;
  padding-left: 8px;
}

#btn_login {
  float: left;
}

#btn_reset {
  float: right;
}

內容解密:

  • .div_login 類別用於設定登入表單的樣式,包括寬度、邊距、背景顏色和圓角。
  • h2 標籤用於設定標題的樣式,使其置中並增加內距。
  • #Uname#Pass 是輸入欄位的 ID,它們的樣式被設定為相同的寬度和高度,以保持一致性。
  • #btn_login#btn_reset 是登入和重設按鈕的 ID,它們分別被設定為浮動在左邊和右邊。

接著,我們需要在 www/index.html 檔案中參照這個 CSS 檔案:

<head>
  <meta charset="utf-8">
  <title>Login Application</title>
  <link rel="stylesheet" type="text/css" href="style.css">
</head>

內容解密:

  • 在 HTML 檔案的 <head> 部分,我們新增了一個 <link> 標籤來參照剛才建立的 style.css 檔案。

完成上述步驟後,我們的登入介面應該會呈現出更好的外觀。

新增互動功能

目前我們的登入和重設按鈕尚未實作任何功能。接下來,我們將為這些按鈕新增點選事件。

首先,我們需要在 www/index.js 檔案中定義兩個函式:click_loginclick_reset,並將它們與對應的按鈕點選事件繫結:

document.getElementById('btn_login').addEventListener('click', ev => click_login());
document.getElementById('btn_reset').addEventListener('click', ev => click_reset());

function click_login() {
  let uname = document.getElementById("Uname").value;
  let pwd = document.getElementById("Pass").value;
  if (wasm.login(uname, pwd)) {
    // 登入成功
  } else {
    wasm.login_failure();
  }
}

function click_reset() {
  document.getElementById("Uname").value = "";
  document.getElementById("Pass").value = "";
}

內容解密:

  • click_login 函式會取得使用者輸入的使用者名稱和密碼,並呼叫 wasm.login 函式進行驗證。
  • 如果驗證成功,則執行相關操作;否則,呼叫 wasm.login_failure 函式顯示錯誤訊息。
  • click_reset 函式會重設使用者名稱和密碼輸入欄位的值。

在 Rust 的 wasm-login/src/lib.rs 檔案中,我們定義了 login 函式:

#[wasm_bindgen]
pub fn login(uname: &str, passwd: &str) -> bool {
  if uname == "abhishek" && passwd == "rust" {
    return true;
  } else {
    return false;
  }
}

內容解密:

  • login 函式接受使用者名稱和密碼作為引數,並與預設的值進行比較。
  • 如果匹配成功,則傳回 true;否則,傳回 false

完成上述步驟後,我們需要重新編譯專案並啟動伺服器:

wasm-pack build
npm install
npm run start

開啟瀏覽器並導航到 http://localhost:8080/,即可看到我們的新登入介面。

測試登入功能

輸入錯誤的使用者名稱或密碼並點選「Log In」按鈕,會觸發一個警告視窗顯示錯誤訊息。

輸入正確的使用者名稱 abhishek 和密碼 rust,並點選「Log In」按鈕,會看到一個歡迎訊息,且使用者名稱和密碼輸入欄位會消失。

點選「Log Out」按鈕,會回到原始的登入介面。