作者:京東物流 梁瑞樂
偶然一次機會,接觸了Rust的代碼。當時想給團隊小伙伴做演示,發(fā)現(xiàn)自己并不能在移動端按照文檔生成演示demo。我就想,要是Rust代碼能轉化成JavaScript就好了。結果一搜,還真有。
下面整理成文檔,分享給大家。為大家解決問題,多提供一種思路、方式、方法。
?
一、分享的目的:
?由 Rust、WebAssembly、JavaScript、HTML 和 CSS 開發(fā)多語言程序的工作流程。
?如何設計 API 以最大限度地利用 Rust 和 WebAssembly 的優(yōu)勢以及 JavaScript 的優(yōu)勢。
?如何調試從 Rust 編譯的 WebAssembly 模塊。
?
二、什么是WebAssembly?
WebAssembly (wasm) 是一種具有廣泛規(guī)范的簡單機器模型和可執(zhí)行格式。它被設計為可移植、緊湊并以本機速度或接近本機速度執(zhí)行。
作為一種編程語言,WebAssembly 由兩種表示相同結構的格式組成,盡管方式不同:
1.該.wat文本格式(稱為wat“WebAssemblyText”)使用S 表達式,與 Scheme 和 Clojure等Lisp 語言家族有相似之處。https://en.wikipedia.org/wiki/S-expression
2.二進制格式.wasm是較低級別的,旨在供 wasm 虛擬機直接使用。它在概念上類似于 ELF 和 Mach-O。
有工具,可以從.wat文本格式到.wasm二進制格式的轉換。
?
三、環(huán)境準備:
需要標準 Rust 工具鏈,包括rustup、rustc和cargo。
安裝參考:https://www.rust-lang.org/tools/install?
?
四、學習網站:
?Rust 和 WebAssembly ?
?wasm-bindgen官網地址?
?
五、練習演示:
下面這段代碼項目是用 Rust + JavaScript 編寫的,用于 WebAssembly (Wasm) 項目,它與 Web Workers 和 Web 頁面交互。代碼的主要功能是判斷用戶輸入的數(shù)字是否為偶數(shù),并將結果顯示在網頁上。
?
1、安裝wasm-pack: wasm-pack是一個幫助你構建和打包Rust代碼到WebAssembly的工具。
cargo install wasm-pack
2、創(chuàng)建一個新的Rust庫項目:
cargo new --lib my_demo cd my_demo
此時,生成文件目錄:

3、配置Cargo.toml: 在Cargo.toml文件中添加wasm-bindgen和web-sys依賴項。
[package] authors = ["The wasm-demo Developers"] edition = "2024" name = "wasm-in-web-worker" publish = false version = "0.0.0" [lib] crate-type = ["cdylib"] [dependencies] console_error_panic_hook = { version = "0.1.6", optional = true } wasm-bindgen = "0.2" web-sys = { version = "0.3", features = ['console', 'Document', 'HtmlElement', 'HtmlInputElement', 'MessageEvent', 'Window', 'Worker'] }
在features中,你可以根據(jù)需要啟用web-sys的特定Web API特性。更多配置,參考學習文檔。
?
4、編寫Rust代碼: 在src/lib.rs中使用web-sys。
// 代碼首先導入了一些 Rust 標準庫和 wasm_bindgen 相關的模塊,這些模塊用于在 Rust 和 JavaScript 之間建立橋梁,以及操作 Web API。
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use web_sys::{console, HtmlElement, HtmlInputElement, MessageEvent, Worker};
// 定義 NumberEval 結構體
// NumberEval 結構體用于存儲一個整數(shù),并提供方法來判斷該整數(shù)是否為偶數(shù)。
// new 方法創(chuàng)建 NumberEval 的新實例,初始數(shù)字為 0。
// is_even 方法接受一個整數(shù)參數(shù),將其存儲在結構體中,并返回該數(shù)字是否為偶數(shù)。
// get_last_number 方法返回結構體中存儲的最后一個數(shù)字
#[wasm_bindgen]
pub struct NumberEval {
number: i32,
}
#[wasm_bindgen]
impl NumberEval {
// Create new instance.
pub fn new() -> NumberEval {
NumberEval { number: 0 }
}
pub fn is_even(&mut self, number: i32) -> bool {
self.number = number;
self.number % 2 == 0
}
pub fn get_last_number(&self) -> i32 {
self.number
}
}
// startup 函數(shù)是在 Wasm 模塊加載時調用的入口點。它創(chuàng)建了一個 Web Worker 實例,并設置了一個輸入框的 oninput 事件回調。
#[wasm_bindgen]
pub fn startup() {
// 創(chuàng)建Web Worker實例
let worker_handle = Rc::new(RefCell::new(Worker::new("./worker.js").unwrap()));
console::log_1(&"Created a new worker from within Wasm".into());
setup_input_oninput_callback(worker_handle);
}
// 定義 setup_input_oninput_callback 函數(shù)
// 這個函數(shù)設置了一個回調函數(shù),當用戶在輸入框中輸入時觸發(fā)。它讀取輸入框的值,嘗試將其解析為整數(shù),并將該整數(shù)發(fā)送到 Web Worker。
// 如果解析失敗,它會清空結果顯示字段。
fn setup_input_oninput_callback(worker: Rc>) {
let document = web_sys::window().unwrap().document().unwrap();
// #[allow(unused_assignments)] 屬性被用來告訴編譯器忽略未使用的賦值警告。這樣,即使value變量被賦值后沒有被使用,編譯器也不會發(fā)出警告。
#[allow(unused_assignments)]
let mut persistent_callback_handle = get_on_msg_callback();
let callback = Closure::new(move || {
console::log_1(&"oninput callback triggered".into());
let document = web_sys::window().unwrap().document().unwrap();
let input_field = document
.get_element_by_id("inputNumber")
.expect("#inputNumber should exist");
let input_field = input_field
.dyn_ref::()
.expect("#inputNumber should be a HtmlInputElement");
match input_field.value().parse::() {
Ok(number) => {
// 代碼中的 Web Worker 交互包括創(chuàng)建 Worker 實例、發(fā)送消息給 Worker (post_message),以及設置 Worker 的 onmessage 事件處理器來接收 Worker 的響應。
let worker_handle = &*worker.borrow();
let _ = worker_handle.post_message(&number.into());
persistent_callback_handle = get_on_msg_callback();
worker_handle
.set_onmessage(Some(persistent_callback_handle.as_ref().unchecked_ref()));
}
Err(_) => {
document
.get_element_by_id("resultField")
.expect("#resultField should exist")
.dyn_ref::()
.expect("#resultField should be a HtmlInputElement")
.set_inner_text("");
}
}
});
document
.get_element_by_id("inputNumber")
.expect("#inputNumber should exist")
.dyn_ref::()
.expect("#inputNumber should be a HtmlInputElement")
.set_oninput(Some(callback.as_ref().unchecked_ref()));
// forget 方法用于防止 Rust 清理閉包,因為閉包將由 JavaScript 管理。
callback.forget();
}
// 定義 get_on_msg_callback 函數(shù)
// 這個函數(shù)創(chuàng)建了一個閉包,用于處理從 Web Worker 返回的消息。
// 它接收一個 MessageEvent,從中提取數(shù)據(jù),并根據(jù)數(shù)據(jù)是 true 還是 false 來更新頁面上的結果顯示字段,顯示 "even" 或 "odd"。
fn get_on_msg_callback() -> Closure {
Closure::new(move |event: MessageEvent| {
console::log_2(&"Received response: ".into(), &event.data());
let result = match event.data().as_bool().unwrap() {
true => "even",
false => "odd",
};
let document = web_sys::window().unwrap().document().unwrap();
document
.get_element_by_id("resultField")
.expect("#resultField should exist")
.dyn_ref::()
.expect("#resultField should be a HtmlInputElement")
.set_inner_text(result);
})
}
注意事項:
?Closure::new 和 Closure::forget 用于創(chuàng)建和管理 Rust 和 JavaScript 之間的閉包。
?Rc> 用于共享對 Worker 的可變引用,允許在多個地方修改 Worker 的狀態(tài)。
?wasm_bindgen 宏用于將 Rust 代碼暴露給 JavaScript,使得 JavaScript 可以調用 Rust 函數(shù)。
?
5、構建項目: 使用wasm-pack構建項目,生成可以在Web環(huán)境中運行的WebAssembly包。
wasm-pack build --target no-modules
--target 后面可以跟的參數(shù),如下圖

?
6、在Web頁面中使用: 創(chuàng)建一個HTML文件,并在其中引入生成的.wasm文件。
index.html如下:
/head?>
與Wasm Web Worker 交互/h1?>
/script?> /script?> /body?> /html?>
// index.js
// `#[wasm_bindgen]`
const {startup} = wasm_bindgen;
async function run_wasm() {
// 加載 Wasm 文件
// 在`index.html`里導入了`wasm_bindgen`
await wasm_bindgen();
console.log('index.js loaded');
// 運行入口方法
// 創(chuàng)建worker實例
startup();
}
run_wasm();
// worker.js
// 這段代碼包含 Web Worker 的實現(xiàn)細節(jié), worker.js 接收到數(shù)字后,會判斷它是否為偶數(shù),并將結果發(fā)送回主線程。
importScripts('http://www.brongaenegriffin.com/images/chaijie_default.png');
console.log('Initializing worker')
// In the worker, we have a different struct that we want to use as in
// `index.js`.
const {NumberEval} = wasm_bindgen;
async function init_wasm_in_worker() {
// Load the Wasm file by awaiting the Promise returned by `wasm_bindgen`.
await wasm_bindgen('./pkg/wasm_in_web_worker_bg.wasm');
// Create a new object of the `NumberEval` struct.
var num_eval = NumberEval.new();
// Set callback to handle messages passed to the worker.
self.onmessage = async event => {
// By using methods of a struct as reaction to messages passed to the
// worker, we can preserve our state between messages.
var worker_result = num_eval.is_even(event.data);
// Send response back to be handled by callback in main thread.
self.postMessage(worker_result);
};
};
init_wasm_in_worker();
7、啟動一個HTTP服務器: 你可以使用任何HTTP服務器來提供你的頁面和WebAssembly模塊。例如,如果你已經安裝了Python,可以使用以下命令:
python3 -m http.server
然后在瀏覽器中打開http://localhost:8000,你應該能看到【Rust 和 WebAssembly 與現(xiàn)有的 JavaScript 工具集成】的網站。

?
8、整體文件目錄如下:

?
六、其他相關工具:
1、wasm-bindgen?
wasm-bindgen促進 Rust 和 JavaScript 之間的高級交互。它允許將 JavaScript 內容導入 Rust 并將 Rust 內容導出到 JavaScript。
2、wasm-bindgen-futures?
wasm-bindgen-futuresPromise是連接 JavaScript和 Rust 的橋梁Future。它可以雙向轉換,在 Rust 中處理異步任務時非常有用,并允許與 DOM 事件和 I/O 操作進行交互。
3、js-sys?
所有 JavaScript 全局類型和方法的原始wasm-bindgen導入,例如Object、等。這些 APIFunction可eval在所有標準 ECMAScript 環(huán)境中移植,而不僅僅是 Web,例如 Node.js。
4、web-sys?
wasm-bindgen所有 Web API 的原始導入,例如 DOM 操作setTimeout、Web GL、Web Audio 等。
?
七、應用場景:
JavaScript 與 Rust 和 WebAssembly (Wasm) 的集成可以應用于多種場景,特別是在需要高性能和/或低級系統(tǒng)訪問的情況下。以下是一些具體的應用場景:
1.性能密集型任務:對于需要大量計算的任務,如圖像或視頻處理、大數(shù)據(jù)分析、復雜算法(如機器學習模型的推斷)等,Rust 生成的 WebAssembly 可以提供比純 JavaScript 更好的性能。
2.游戲開發(fā):WebAssembly 可以使開發(fā)者將現(xiàn)有的高性能游戲引擎(如Unity 或 Unreal Engine)移植到網頁上,或者使用 Rust 編寫自定義的游戲邏輯,以實現(xiàn)接近原生的性能。
3.加密和安全:Rust 提供了內存安全的保證,這對于加密算法和安全相關的代碼非常重要。使用 Rust 編寫的 WebAssembly 模塊可以在客戶端執(zhí)行加密操作,而不必擔心內存安全漏洞。
4.物聯(lián)網 (IoT) 和邊緣計算:Rust 和 WebAssembly 的組合可以用于在瀏覽器之外的環(huán)境中運行,例如在支持 WebAssembly 的 IoT 設備或邊緣計算節(jié)點上。這允許開發(fā)者在這些環(huán)境中運行高性能的應用程序。
5.桌面應用:通過技術如 Electron 或 Tauri,開發(fā)者可以創(chuàng)建跨平臺的桌面應用。Rust 和 WebAssembly 可以用于這些應用中的性能關鍵部分,以提高整體性能。
6.文件壓縮和解壓縮:文件處理操作,如壓縮和解壓縮,可以通過 Rust 和 WebAssembly 實現(xiàn),以提高處理速度并減少在客戶端執(zhí)行這些操作的時間。
7.實時通信:對于需要低延遲的實時通信應用,如在線協(xié)作工具、實時游戲等,Rust 和 WebAssembly 可以提供必要的性能優(yōu)勢。
8.自定義渲染器:對于需要自定義渲染管線的應用,如圖形編輯器或數(shù)據(jù)可視化工具,Rust 和 WebAssembly 可以提供更接近硬件的控制和更好的性能。
9.移植現(xiàn)有的 Rust 庫:許多有用的 Rust 庫可以被編譯成 WebAssembly,使得它們可以在網頁應用中使用,擴展了 JavaScript 的能力。
10.替代插件:對于傳統(tǒng)上依賴于 NPAPI 插件(如 Flash 或 Java Applets)的功能,WebAssembly 提供了一個更安全、更現(xiàn)代的替代方案。
在集成 Rust 和 WebAssembly 到 JavaScript 項目中時,通常會使用 JavaScript 作為“膠水代碼”,處理 DOM 操作、網絡請求等,而將計算密集型或需要優(yōu)化性能的部分交給 Rust 編寫的 WebAssembly 模塊處理。這種方式可以結合 Rust 的性能和安全性以及 JavaScript 的靈活性和生態(tài)系統(tǒng)。
?
八、小結:
1、主要是為大家解決問題,多提供一種思路、方式、方法;
2、Rust 和 WebAssembly與JavaScript集成優(yōu)勢:
?性能提升: Rust 編譯到 WebAssembly 可以提供接近原生的性能,特別是在計算密集型任務中,這通常比 JavaScript 執(zhí)行得更快。
?類型安全: Rust 是一種靜態(tài)類型語言,提供了編譯時類型檢查,這有助于減少運行時錯誤。
?內存安全: Rust 的所有權和借用機制確保了內存安全,沒有垃圾收集器的開銷,這在 WebAssembly 中同樣適用。
?并發(fā)編程: Rust 的并發(fā)編程模型比 JavaScript 的并發(fā)模型(基于事件循環(huán)和回調)更為強大和靈活。
?現(xiàn)代工具鏈:Rust 的`cargo`工具鏈和`wasm-pack`等工具提供了強大的依賴管理和構建工具。
?生態(tài)系統(tǒng):Rust 的生態(tài)系統(tǒng)正在快速增長,提供了大量的庫和框架。
?跨平臺兼容性:WebAssembly 是跨平臺的,可以在所有主流瀏覽器上運行。
3、Rust 和 WebAssembly與JavaScript集成劣勢:
?學習曲線:對于熟悉 JavaScript 的開發(fā)者來說,Rust 的學習曲線可能會比較陡峭。
?工具集成:盡管 Rust 和 WebAssembly 的工具正在改進,但它們與現(xiàn)有的 JavaScript 工具和生態(tài)系統(tǒng)(如 npm, webpack 等)的集成可能不如純 JavaScript 項目那樣無縫。
?啟動時間和文件大?。篧ebAssembly 模塊可能需要額外的加載時間,尤其是當模塊很大時。雖然 Wasm 文件通常比等效的 JavaScript 文件小,但是需要額外的解析和編譯時間。
?DOM 和 Web API 交互:直接從 Rust/WebAssembly 與 DOM 進行交互比從 JavaScript 進行交互更復雜,通常需要通過 JavaScript 中間層或使用像`web-sys`這樣的庫。
?調試支持:雖然 WebAssembly 的調試工具在不斷改進,但它們通常不如 JavaScript 的調試工具成熟和易用。
?社區(qū)和資源:JavaScript 擁有一個龐大的社區(qū)和大量的資源,而 Rust 和 WebAssembly 相對較新,社區(qū)和資源可能沒有那么豐富。
?瀏覽器兼容性:雖然 WebAssembly 在所有現(xiàn)代瀏覽器上都得到了支持,但在一些舊的瀏覽器或者某些移動設備上可能不被支持。
總的來說,Rust 和 WebAssembly 在性能和安全性方面提供了顯著的優(yōu)勢,但在易用性、工具集成和社區(qū)支持方面可能存在一些挑戰(zhàn)。對于需要高性能計算的應用程序,或者那些對安全性有嚴格要求的項目,使用 Rust 和 WebAssembly 可能是一個很好的選擇。然而,對于需要快速開發(fā)和廣泛社區(qū)支持的項目,純JavaScript 解決方案可能更加合適。
歡迎大家留言或私信我,我們共同探討、學習,并且相互提供寶貴的反饋和建議。
-
代碼
+關注
關注
30文章
4882瀏覽量
70090 -
javascript
+關注
關注
0文章
525瀏覽量
54399 -
語言程序
+關注
關注
0文章
5瀏覽量
6028 -
Rust
+關注
關注
1文章
233瀏覽量
6909
發(fā)布評論請先 登錄
WebAssembly技術_編譯ffmpeg(ubuntu20.04)
Rust語言如何與 InfluxDB 集成
RUST在嵌入式開發(fā)中的應用是什么
在Rust代碼中加載靜態(tài)庫時,出現(xiàn)錯誤 ` rust-lld: error: undefined symbol: malloc `怎么解決?
WebAssembly的起源及實踐分析
.NET應用程序可以直接調用WebAssembly模塊了
WebAssembly中的BL602/BL604模擬器使用
基于Rust 編程語言的小游戲程序實例
在WebAssembly中使用Rust編寫eBPF程序并發(fā)布OCI鏡像
Go/Rust挑戰(zhàn)Java/Python地位
使用C++編寫通用庫并在 Rust 中使用它 (WASI)
javascript:;怎么解決
[鴻蒙]OpenHarmony4.0的Rust開發(fā)

JavaScript與Rust和WebAssembly集成
評論