作者:京東物流 梁瑞樂
偶然一次機(jī)會(huì),接觸了Rust的代碼。當(dāng)時(shí)想給團(tuán)隊(duì)小伙伴做演示,發(fā)現(xiàn)自己并不能在移動(dòng)端按照文檔生成演示demo。我就想,要是Rust代碼能轉(zhuǎn)化成JavaScript就好了。結(jié)果一搜,還真有。
下面整理成文檔,分享給大家。為大家解決問題,多提供一種思路、方式、方法。
?
一、分享的目的:
?由 Rust、WebAssembly、JavaScript、HTML 和 CSS 開發(fā)多語言程序的工作流程。
?如何設(shè)計(jì) API 以最大限度地利用 Rust 和 WebAssembly 的優(yōu)勢(shì)以及 JavaScript 的優(yōu)勢(shì)。
?如何調(diào)試從 Rust 編譯的 WebAssembly 模塊。
?
二、什么是WebAssembly?
WebAssembly (wasm) 是一種具有廣泛規(guī)范的簡(jiǎn)單機(jī)器模型和可執(zhí)行格式。它被設(shè)計(jì)為可移植、緊湊并以本機(jī)速度或接近本機(jī)速度執(zhí)行。
作為一種編程語言,WebAssembly 由兩種表示相同結(jié)構(gòu)的格式組成,盡管方式不同:
1.該.wat文本格式(稱為wat“WebAssemblyText”)使用S 表達(dá)式,與 Scheme 和 Clojure等Lisp 語言家族有相似之處。https://en.wikipedia.org/wiki/S-expression
2.二進(jìn)制格式.wasm是較低級(jí)別的,旨在供 wasm 虛擬機(jī)直接使用。它在概念上類似于 ELF 和 Mach-O。
有工具,可以從.wat文本格式到.wasm二進(jìn)制格式的轉(zhuǎn)換。
?
三、環(huán)境準(zhǔn)備:
需要標(biāo)準(zhǔn) Rust 工具鏈,包括rustup、rustc和cargo。
安裝參考:https://www.rust-lang.org/tools/install?
?
四、學(xué)習(xí)網(wǎng)站:
?Rust 和 WebAssembly ?
?wasm-bindgen官網(wǎng)地址?
?
五、練習(xí)演示:
下面這段代碼項(xiàng)目是用 Rust + JavaScript 編寫的,用于 WebAssembly (Wasm) 項(xiàng)目,它與 Web Workers 和 Web 頁面交互。代碼的主要功能是判斷用戶輸入的數(shù)字是否為偶數(shù),并將結(jié)果顯示在網(wǎng)頁上。
?
1、安裝wasm-pack: wasm-pack是一個(gè)幫助你構(gòu)建和打包Rust代碼到WebAssembly的工具。
cargo install wasm-pack
2、創(chuàng)建一個(gè)新的Rust庫項(xiàng)目:
cargo new --lib my_demo cd my_demo
此時(shí),生成文件目錄:
3、配置Cargo.toml: 在Cargo.toml文件中添加wasm-bindgen和web-sys依賴項(xiàng)。
[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特性。更多配置,參考學(xué)習(xí)文檔。
?
4、編寫Rust代碼: 在src/lib.rs中使用web-sys。
// 代碼首先導(dǎo)入了一些 Rust 標(biāo)準(zhǔn)庫和 wasm_bindgen 相關(guān)的模塊,這些模塊用于在 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 結(jié)構(gòu)體 // NumberEval 結(jié)構(gòu)體用于存儲(chǔ)一個(gè)整數(shù),并提供方法來判斷該整數(shù)是否為偶數(shù)。 // new 方法創(chuàng)建 NumberEval 的新實(shí)例,初始數(shù)字為 0。 // is_even 方法接受一個(gè)整數(shù)參數(shù),將其存儲(chǔ)在結(jié)構(gòu)體中,并返回該數(shù)字是否為偶數(shù)。 // get_last_number 方法返回結(jié)構(gòu)體中存儲(chǔ)的最后一個(gè)數(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 模塊加載時(shí)調(diào)用的入口點(diǎn)。它創(chuàng)建了一個(gè) Web Worker 實(shí)例,并設(shè)置了一個(gè)輸入框的 oninput 事件回調(diào)。 #[wasm_bindgen] pub fn startup() { // 創(chuàng)建Web Worker實(shí)例 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ù) // 這個(gè)函數(shù)設(shè)置了一個(gè)回調(diào)函數(shù),當(dāng)用戶在輸入框中輸入時(shí)觸發(fā)。它讀取輸入框的值,嘗試將其解析為整數(shù),并將該整數(shù)發(fā)送到 Web Worker。 // 如果解析失敗,它會(huì)清空結(jié)果顯示字段。 fn setup_input_oninput_callback(worker: Rc>) { let document = web_sys::window().unwrap().document().unwrap(); // #[allow(unused_assignments)] 屬性被用來告訴編譯器忽略未使用的賦值警告。這樣,即使value變量被賦值后沒有被使用,編譯器也不會(huì)發(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 實(shí)例、發(fā)送消息給 Worker (post_message),以及設(shè)置 Worker 的 onmessage 事件處理器來接收 Worker 的響應(yīng)。 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 清理閉包,因?yàn)殚]包將由 JavaScript 管理。 callback.forget(); } // 定義 get_on_msg_callback 函數(shù) // 這個(gè)函數(shù)創(chuàng)建了一個(gè)閉包,用于處理從 Web Worker 返回的消息。 // 它接收一個(gè) MessageEvent,從中提取數(shù)據(jù),并根據(jù)數(shù)據(jù)是 true 還是 false 來更新頁面上的結(jié)果顯示字段,顯示 "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); }) }
注意事項(xiàng):
?Closure::new 和 Closure::forget 用于創(chuàng)建和管理 Rust 和 JavaScript 之間的閉包。
?Rc> 用于共享對(duì) Worker 的可變引用,允許在多個(gè)地方修改 Worker 的狀態(tài)。
?wasm_bindgen 宏用于將 Rust 代碼暴露給 JavaScript,使得 JavaScript 可以調(diào)用 Rust 函數(shù)。
?
5、構(gòu)建項(xiàng)目: 使用wasm-pack構(gòu)建項(xiàng)目,生成可以在Web環(huán)境中運(yùn)行的WebAssembly包。
wasm-pack build --target no-modules
--target 后面可以跟的參數(shù),如下圖
?
6、在Web頁面中使用: 創(chuàng)建一個(gè)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`里導(dǎo)入了`wasm_bindgen` await wasm_bindgen(); console.log('index.js loaded'); // 運(yùn)行入口方法 // 創(chuàng)建worker實(shí)例 startup(); } run_wasm();
// worker.js // 這段代碼包含 Web Worker 的實(shí)現(xiàn)細(xì)節(jié), worker.js 接收到數(shù)字后,會(huì)判斷它是否為偶數(shù),并將結(jié)果發(fā)送回主線程。 importScripts('http://m.1cnz.cn/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、啟動(dòng)一個(gè)HTTP服務(wù)器: 你可以使用任何HTTP服務(wù)器來提供你的頁面和WebAssembly模塊。例如,如果你已經(jīng)安裝了Python,可以使用以下命令:
python3 -m http.server
然后在瀏覽器中打開http://localhost:8000,你應(yīng)該能看到【Rust 和 WebAssembly 與現(xiàn)有的 JavaScript 工具集成】的網(wǎng)站。
?
8、整體文件目錄如下:
?
六、其他相關(guān)工具:
1、wasm-bindgen?
wasm-bindgen促進(jìn) Rust 和 JavaScript 之間的高級(jí)交互。它允許將 JavaScript 內(nèi)容導(dǎo)入 Rust 并將 Rust 內(nèi)容導(dǎo)出到 JavaScript。
2、wasm-bindgen-futures?
wasm-bindgen-futuresPromise是連接 JavaScript和 Rust 的橋梁Future。它可以雙向轉(zhuǎn)換,在 Rust 中處理異步任務(wù)時(shí)非常有用,并允許與 DOM 事件和 I/O 操作進(jìn)行交互。
3、js-sys?
所有 JavaScript 全局類型和方法的原始wasm-bindgen導(dǎo)入,例如Object、等。這些 APIFunction可eval在所有標(biāo)準(zhǔn) ECMAScript 環(huán)境中移植,而不僅僅是 Web,例如 Node.js。
4、web-sys?
wasm-bindgen所有 Web API 的原始導(dǎo)入,例如 DOM 操作setTimeout、Web GL、Web Audio 等。
?
七、應(yīng)用場(chǎng)景:
JavaScript 與 Rust 和 WebAssembly (Wasm) 的集成可以應(yīng)用于多種場(chǎng)景,特別是在需要高性能和/或低級(jí)系統(tǒng)訪問的情況下。以下是一些具體的應(yīng)用場(chǎng)景:
1.性能密集型任務(wù):對(duì)于需要大量計(jì)算的任務(wù),如圖像或視頻處理、大數(shù)據(jù)分析、復(fù)雜算法(如機(jī)器學(xué)習(xí)模型的推斷)等,Rust 生成的 WebAssembly 可以提供比純 JavaScript 更好的性能。
2.游戲開發(fā):WebAssembly 可以使開發(fā)者將現(xiàn)有的高性能游戲引擎(如Unity 或 Unreal Engine)移植到網(wǎng)頁上,或者使用 Rust 編寫自定義的游戲邏輯,以實(shí)現(xiàn)接近原生的性能。
3.加密和安全:Rust 提供了內(nèi)存安全的保證,這對(duì)于加密算法和安全相關(guān)的代碼非常重要。使用 Rust 編寫的 WebAssembly 模塊可以在客戶端執(zhí)行加密操作,而不必?fù)?dān)心內(nèi)存安全漏洞。
4.物聯(lián)網(wǎng) (IoT) 和邊緣計(jì)算:Rust 和 WebAssembly 的組合可以用于在瀏覽器之外的環(huán)境中運(yùn)行,例如在支持 WebAssembly 的 IoT 設(shè)備或邊緣計(jì)算節(jié)點(diǎn)上。這允許開發(fā)者在這些環(huán)境中運(yùn)行高性能的應(yīng)用程序。
5.桌面應(yīng)用:通過技術(shù)如 Electron 或 Tauri,開發(fā)者可以創(chuàng)建跨平臺(tái)的桌面應(yīng)用。Rust 和 WebAssembly 可以用于這些應(yīng)用中的性能關(guān)鍵部分,以提高整體性能。
6.文件壓縮和解壓縮:文件處理操作,如壓縮和解壓縮,可以通過 Rust 和 WebAssembly 實(shí)現(xiàn),以提高處理速度并減少在客戶端執(zhí)行這些操作的時(shí)間。
7.實(shí)時(shí)通信:對(duì)于需要低延遲的實(shí)時(shí)通信應(yīng)用,如在線協(xié)作工具、實(shí)時(shí)游戲等,Rust 和 WebAssembly 可以提供必要的性能優(yōu)勢(shì)。
8.自定義渲染器:對(duì)于需要自定義渲染管線的應(yīng)用,如圖形編輯器或數(shù)據(jù)可視化工具,Rust 和 WebAssembly 可以提供更接近硬件的控制和更好的性能。
9.移植現(xiàn)有的 Rust 庫:許多有用的 Rust 庫可以被編譯成 WebAssembly,使得它們可以在網(wǎng)頁應(yīng)用中使用,擴(kuò)展了 JavaScript 的能力。
10.替代插件:對(duì)于傳統(tǒng)上依賴于 NPAPI 插件(如 Flash 或 Java Applets)的功能,WebAssembly 提供了一個(gè)更安全、更現(xiàn)代的替代方案。
在集成 Rust 和 WebAssembly 到 JavaScript 項(xiàng)目中時(shí),通常會(huì)使用 JavaScript 作為“膠水代碼”,處理 DOM 操作、網(wǎng)絡(luò)請(qǐng)求等,而將計(jì)算密集型或需要優(yōu)化性能的部分交給 Rust 編寫的 WebAssembly 模塊處理。這種方式可以結(jié)合 Rust 的性能和安全性以及 JavaScript 的靈活性和生態(tài)系統(tǒng)。
?
八、小結(jié):
1、主要是為大家解決問題,多提供一種思路、方式、方法;
2、Rust 和 WebAssembly與JavaScript集成優(yōu)勢(shì):
?性能提升: Rust 編譯到 WebAssembly 可以提供接近原生的性能,特別是在計(jì)算密集型任務(wù)中,這通常比 JavaScript 執(zhí)行得更快。
?類型安全: Rust 是一種靜態(tài)類型語言,提供了編譯時(shí)類型檢查,這有助于減少運(yùn)行時(shí)錯(cuò)誤。
?內(nèi)存安全: Rust 的所有權(quán)和借用機(jī)制確保了內(nèi)存安全,沒有垃圾收集器的開銷,這在 WebAssembly 中同樣適用。
?并發(fā)編程: Rust 的并發(fā)編程模型比 JavaScript 的并發(fā)模型(基于事件循環(huán)和回調(diào))更為強(qiáng)大和靈活。
?現(xiàn)代工具鏈:Rust 的`cargo`工具鏈和`wasm-pack`等工具提供了強(qiáng)大的依賴管理和構(gòu)建工具。
?生態(tài)系統(tǒng):Rust 的生態(tài)系統(tǒng)正在快速增長(zhǎng),提供了大量的庫和框架。
?跨平臺(tái)兼容性:WebAssembly 是跨平臺(tái)的,可以在所有主流瀏覽器上運(yùn)行。
3、Rust 和 WebAssembly與JavaScript集成劣勢(shì):
?學(xué)習(xí)曲線:對(duì)于熟悉 JavaScript 的開發(fā)者來說,Rust 的學(xué)習(xí)曲線可能會(huì)比較陡峭。
?工具集成:盡管 Rust 和 WebAssembly 的工具正在改進(jìn),但它們與現(xiàn)有的 JavaScript 工具和生態(tài)系統(tǒng)(如 npm, webpack 等)的集成可能不如純 JavaScript 項(xiàng)目那樣無縫。
?啟動(dòng)時(shí)間和文件大小:WebAssembly 模塊可能需要額外的加載時(shí)間,尤其是當(dāng)模塊很大時(shí)。雖然 Wasm 文件通常比等效的 JavaScript 文件小,但是需要額外的解析和編譯時(shí)間。
?DOM 和 Web API 交互:直接從 Rust/WebAssembly 與 DOM 進(jìn)行交互比從 JavaScript 進(jìn)行交互更復(fù)雜,通常需要通過 JavaScript 中間層或使用像`web-sys`這樣的庫。
?調(diào)試支持:雖然 WebAssembly 的調(diào)試工具在不斷改進(jìn),但它們通常不如 JavaScript 的調(diào)試工具成熟和易用。
?社區(qū)和資源:JavaScript 擁有一個(gè)龐大的社區(qū)和大量的資源,而 Rust 和 WebAssembly 相對(duì)較新,社區(qū)和資源可能沒有那么豐富。
?瀏覽器兼容性:雖然 WebAssembly 在所有現(xiàn)代瀏覽器上都得到了支持,但在一些舊的瀏覽器或者某些移動(dòng)設(shè)備上可能不被支持。
總的來說,Rust 和 WebAssembly 在性能和安全性方面提供了顯著的優(yōu)勢(shì),但在易用性、工具集成和社區(qū)支持方面可能存在一些挑戰(zhàn)。對(duì)于需要高性能計(jì)算的應(yīng)用程序,或者那些對(duì)安全性有嚴(yán)格要求的項(xiàng)目,使用 Rust 和 WebAssembly 可能是一個(gè)很好的選擇。然而,對(duì)于需要快速開發(fā)和廣泛社區(qū)支持的項(xiàng)目,純JavaScript 解決方案可能更加合適。
歡迎大家留言或私信我,我們共同探討、學(xué)習(xí),并且相互提供寶貴的反饋和建議。
-
代碼
+關(guān)注
關(guān)注
30文章
4823瀏覽量
68896 -
javascript
+關(guān)注
關(guān)注
0文章
525瀏覽量
53909 -
語言程序
+關(guān)注
關(guān)注
0文章
5瀏覽量
5960 -
Rust
+關(guān)注
關(guān)注
1文章
230瀏覽量
6641
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論