溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JavaScript和Rust中wasm-bindgen組件有什么用

發(fā)布時(shí)間:2021-08-06 10:35:01 來源:億速云 閱讀:224 作者:小新 欄目:web開發(fā)

這篇文章主要介紹JavaScript和Rust中wasm-bindgen組件有什么用,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

WebAssembly標(biāo)準(zhǔn)只定義了四種類型:兩種整數(shù)類型和兩種浮點(diǎn)類型。然而,大多數(shù)情況下,JS和Rust開發(fā)人員正在使用更豐富的類型! 例如,JS開發(fā)人員經(jīng)常與互以添加或修改HTML節(jié)點(diǎn)相關(guān)的文檔交互,而Rust開發(fā)人員使用類似Result等類型進(jìn)行錯(cuò)誤處理,幾乎所有程序員都使用字符串。

被局限在僅使用由WebAssembly所提供的類型將會(huì)受到太多的限制,這就是wasm-bindgen出現(xiàn)的原因。

wasm-bindgen的目標(biāo)是提供一個(gè)JS和Rust類型之間的橋接。它允許JS使用字符串調(diào)用Rust API,或Rust函數(shù)捕獲JS異常。

wasm-bindgen抹平了WebAssembly和JavaScript之間的阻抗失配,確保JavaScript可以高效地調(diào)用WebAssembly函數(shù),并且無需boilerplate,同時(shí)WebAssembly可以對(duì)JavaScript函數(shù)執(zhí)行相同的操作。

wasm-bindgen項(xiàng)目在其README文件中有更多描述。要入門,讓我們深入到一個(gè)使用wasm-bindgen的例子中,然后探索它還有提供了什么。

1、Hello World!

學(xué)習(xí)新工具的最好也是最經(jīng)典的方法之一就是探索下用它來輸出“Hello, World!”。在這里,我們將探索一個(gè)這樣的例子——在頁(yè)面里彈出“Hello World!”提醒框。

這里的目標(biāo)很簡(jiǎn)單,我們想要定義一個(gè)Rust的函數(shù),給定一個(gè)名字,它會(huì)在頁(yè)面上創(chuàng)建一個(gè)對(duì)話框,上面寫著Hello,$name!在JavaScript中,我們可以將這個(gè)函數(shù)定義為:

代碼

export function greet(name) {
  alert(`Hello, ${name}!`);
}

不過在這個(gè)例子里要注意的是,我們將把它用Rust編寫。這里已經(jīng)發(fā)生了很多我們必須要處理的事情:

  • JavaScript將會(huì)調(diào)用一個(gè)WebAssembly 模塊, 模塊名是 greetexport.

  • Rust函數(shù)將一個(gè)字符串作為輸入?yún)?shù),也就是我們要打招呼的名字。

  • 在內(nèi)部Rust會(huì)生成一個(gè)新的字符串,也就是傳入的名字。

  • 最后Rust會(huì)調(diào)用JavaScript的 alert函數(shù),以剛創(chuàng)建的字符串作為參數(shù)。

啟動(dòng)第一步,我們創(chuàng)建一個(gè)新的Rust工程:

代碼

$ cargo new wasm-greet --lib

這將初始化一個(gè)新的wasm-greet文件夾,我們的工作都在這里面完成。接下來我們要使用如下信息修改我們的Cargo.toml(在Rust里相當(dāng)于package.json):

代碼

[lib] 
crate-type = ["cdylib"] 
 
[dependencies] 
wasm-bindgen = "0.2"

我們先忽略[lib]節(jié)的內(nèi)容,接下來的部分聲明了對(duì)wasm-bindgen的依賴。這里的依賴包含了我們使用wasm-bindgen需要的所有的支持包。

接下來,是時(shí)候編寫一些代碼了!我們使用下列內(nèi)容替換了自動(dòng)創(chuàng)建的src/lib.rs:

代碼

#![feature(proc_macro, wasm_custom_section, wasm_import_module)]

extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
  fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
  alert(&format!("Hello, {}!", name));
}

如果你不熟悉Rust,這可能看起來有點(diǎn)啰嗦,但不要害怕!隨著時(shí)間的推移,wasm-bindgen項(xiàng)目不斷改進(jìn),而且可以肯定的是,所有這些并不總是必要的。

要注意的最重要的一點(diǎn)是#[wasm_bindgen]屬性,這是一個(gè)在Rust代碼中的注釋,這里的意思是“請(qǐng)?jiān)诒匾獣r(shí)用wrapper處理這個(gè)”。我們對(duì)alert函數(shù)的導(dǎo)入和greet函數(shù)的導(dǎo)出都被標(biāo)注為這個(gè)屬性。稍后,我們將看到在引擎蓋下發(fā)生了什么。

首先,我們從在瀏覽器中打開作為例子來切入正題!我們先編譯wasm代碼:

代碼

$ rustup target add wasm32-unknown-unknown --toolchain nightly # only needed once 
$ cargo +nightly build --target wasm32-unknown-unknown

這段代碼會(huì)生成一個(gè)wasm文件,路徑為target/wasm32-unknown-unknown/debug/wasm_greet.wasm。如果我們使用工具如wasm2wat來看這個(gè)wasm文件里面的內(nèi)容,可能會(huì)有點(diǎn)嚇人。

結(jié)果發(fā)現(xiàn)這個(gè)wasm文件實(shí)際上還不能直接被JS調(diào)用!為了能讓我們使用,我們需要執(zhí)行一個(gè)或更多步驟:

代碼

$ cargo install wasm-bindgen-cli # only needed once 
$ wasm-bindgen target/wasm32-unknown-unknown/debug/wasm_greet.wasm --out-dir .

很多不可思議的事情發(fā)生都發(fā)生在這個(gè)步驟中:wasm-bindgen CLI工具對(duì)輸入的wasm文件做后期處理,使它變的“suitable”可用。

我們待會(huì)再來看“suitable”的意思,現(xiàn)在我們可以肯定的說,如果我們引入剛創(chuàng)建的wasm_greet.js文件(wasm-bindgen工具創(chuàng)建的),我們已經(jīng)獲取到了在Rust中定義的greet函數(shù)。

最終我們接下來要做的是使用bundler對(duì)其打包,然后創(chuàng)建一個(gè)HTML頁(yè)面運(yùn)行我們的代碼。

在寫這篇文章的時(shí)候,只有Webpack's 4.0 release對(duì)WebAssembly的使用有足夠的支持(盡管暫時(shí)已經(jīng)有了 Chrome caveat)。

總有一天,更多的bundler也會(huì)接著支持WebAssmbly。在這我不再描述細(xì)節(jié),但是你可以看一下在Github倉(cāng)庫(kù)里的example配置。不過如果我們看內(nèi)容,這個(gè)頁(yè)面中我們的JS在看起來是這樣的:
代碼

const rust = import("./wasm_greet"); 
rust.then(m => m.greet("World!"));

…就是這些了!現(xiàn)在打開我們的網(wǎng)頁(yè)就會(huì)顯示一個(gè)不錯(cuò)的“Hello, World!”對(duì)話框,這就是Rust驅(qū)動(dòng)的。

2、wasm-bindgen是如何工作的

唷,那是一個(gè)巨大的“Hello, World!”。讓我們深入了解一下更多的細(xì)節(jié),以了解后臺(tái)發(fā)生了什么以及該工具是如何工作的。

wasm-bindgen最重要的方面之一就是它的集成基本上是建立在一個(gè)概念之上的,即一個(gè)wasm模塊僅是另一種ES模塊。例如,在上述中我們想要一個(gè)帶有如下簽名的ES模塊(在Typescript中):

代碼

export function greet(s: string);

WebAssembly無法在本地執(zhí)行此操作(請(qǐng)記住,它目前只支持?jǐn)?shù)字),所以我們依靠wasm-bindgen來填補(bǔ)空白。

在上述的最后一步中,當(dāng)我們運(yùn)行wasm-bindgen工具時(shí),你會(huì)注意到wasm_greet.js文件與wasm_greet_bg.wasm文件一起出現(xiàn)。前者是我們想要的實(shí)際JS接口,執(zhí)行任何必要的處理以調(diào)用Rust。* _bg.wasm文件包含實(shí)際的實(shí)現(xiàn)和我們所有的編譯后的代碼。

我們可以通過引入 ./wasm_greet 模塊得到 Rust 代碼愿意暴露出來的東西。我們已經(jīng)看到了是如何集成的,可以繼續(xù)看看執(zhí)行的結(jié)果如何。首先是我們的示例:

代碼

const rust = import("./wasm_greet"); 
rust.then(m => m.greet("World!"));

我們?cè)谶@里以異步的方式導(dǎo)入接口,等待導(dǎo)入完成(下載和編譯 wasm)。然后調(diào)用模塊的 greet 函數(shù)。

注: 這里用到的異步加載目前需要 Webpack 來實(shí)現(xiàn),但總會(huì)不需要的。而且,其它打包工具可能沒有此功能。

如果我們看看由 wasm-bindgen 工具為 wasm_greet.js 文件生成的內(nèi)容,會(huì)看到像這樣的代碼:

代碼

import * as wasm from './wasm_greet_bg';

// ...

export function greet(arg0) {
  const [ptr0, len0] = passStringToWasm(arg0);
  try {
    const ret = wasm.greet(ptr0, len0);
    return ret;
  } finally {
    wasm.__wbindgen_free(ptr0, len0);
  }
}

export function __wbg_f_alert_alert_n(ptr0, len0) {
  // ...
}

注: 記住這是生成的,未經(jīng)優(yōu)化的代碼,它可能既不優(yōu)雅也不簡(jiǎn)潔??!在 Rust 中通過 LTO(Link Time Optimization,連接時(shí)優(yōu)化)創(chuàng)建新的發(fā)行版,再通過 JS 打包工具流程(壓縮)之后,可能會(huì)精簡(jiǎn)一些。

現(xiàn)在可以了解如何使用wasm-bindgen來生成greet函數(shù)。在底層它仍然調(diào)用wasm的greet函數(shù),但是它是用一個(gè)指針和長(zhǎng)度來調(diào)用的而不是用字符串。

了解passStringToWasm的更多細(xì)節(jié)可以訪問Lin Clark's previous post。它包含了所有的模板,對(duì)我們來說這是除了wasm-bindgen工具以外還需要去寫的東西!然后我們接下來看__wbg_f_alert_alert_n函數(shù)。

進(jìn)入更深一層,下一個(gè)我們感興趣的就是WebAssmbly中的greet函數(shù)。為了了解這個(gè),我們先來看Rust編譯器能訪問到的代碼。注意像上面生成的這種JS wrapper,在這里你不用寫greet的導(dǎo)出符號(hào),#[wasm_bindgen]屬性會(huì)生成一個(gè)shim,由它來為你翻譯,命名如下:

代碼

pub fn greet(name: &str) {
  alert(&format!("Hello, {}!", name));
}

#[export_name = "greet"]
pub extern fn __wasm_bindgen_generated_greet(arg0_ptr: *mut u8, arg0_len: usize) {
  let arg0 = unsafe { ::std::slice::from_raw_parts(arg0_ptr as *const u8, arg0_len) }
  let arg0 = unsafe { ::std::str::from_utf8_unchecked(arg0) };
  greet(arg0);
}

現(xiàn)在可以看到原始代碼,greet,也就是由#[wasm_bindgen]屬性插入的看起來有意思的函數(shù)__wasm_bindgen_generated_greet。這是一個(gè)導(dǎo)出函數(shù)(用#[export_name]和extern關(guān)鍵詞來指定的),參數(shù)為JS傳進(jìn)來的指針/長(zhǎng)度對(duì)。在函數(shù)中它會(huì)將這個(gè)指針/長(zhǎng)度轉(zhuǎn)換為一個(gè)&str (Rust中的一個(gè)字符串),然后將它傳遞給我們定義的greet函數(shù)。

從另一個(gè)方面看,#[wasm_bindgen]屬性生成了兩個(gè)wrappers:一個(gè)是在JavaScript中將JS類型的轉(zhuǎn)換為wasm,另外一個(gè)是在Rust中接收wasm類型并將其轉(zhuǎn)為Rust類型。

現(xiàn)在我們來看wrappers的最后一塊,即alert函數(shù)。Rust中的greet函數(shù)使用標(biāo)準(zhǔn)format!宏來創(chuàng)建一個(gè)新的字符串然后傳給alert?;叵氘?dāng)我們聲明alert方法的時(shí)候,我們是使用 #[wasm_bindgen]聲明的,現(xiàn)在我們看看在這個(gè)函數(shù)中暴露給rustc的內(nèi)容:

代碼

fn alert(s: &str) {
  #[wasm_import_module = "__wbindgen_placeholder__"]
  extern {
    fn __wbg_f_alert_alert_n(s_ptr: *const u8, s_len: usize);
  }
  unsafe {
    let s_ptr = s.as_ptr();
    let s_len = s.len();
    __wbg_f_alert_alert_n(s_ptr, s_len);
  }
}

這并不是我們寫的,但是我們可以看看它是怎么變成這樣的。alert函數(shù)事實(shí)上是一個(gè)簡(jiǎn)化的wrapper,它帶有Rust的 &str然后將它轉(zhuǎn)換為wasm類型(數(shù)字)。它調(diào)用了我們?cè)谏厦婵吹竭^的比較有意思的函數(shù)__wbg_f_alert_alert_n,然而它奇怪的一點(diǎn)就是#[wasm_import_module]屬性。

在WebAssembly中所有導(dǎo)入的函數(shù)都有一個(gè)其存在的模塊,而且由于wasm-bindgen構(gòu)建在ES模塊之上,所以這也將被轉(zhuǎn)譯為ES模塊導(dǎo)入!

目前__wbindgen_placeholder__模塊實(shí)際上并不存在,但它表示該導(dǎo)入將被wasm-bindgen工具重寫,以從我們生成的JS文件中導(dǎo)入。

最后,對(duì)于最后一部分的疑惑,我們得到了我們所生成的JS文件,其中包含:

代碼

export function __wbg_f_alert_alert_n(ptr0, len0) {
  let arg0 = getStringFromWasm(ptr0, len0);
  alert(arg0)
}

哇! 事實(shí)證明,這里隱藏著相當(dāng)多的東西,我們從JS中的瀏覽器中的警告都有一個(gè)相對(duì)較長(zhǎng)的知識(shí)鏈。不過,不要害怕,wasm-bindgen的核心是所有這些基礎(chǔ)設(shè)施都被隱藏了! 你只需要在隨便使用幾個(gè)#[wasm_bindgen]編寫Rust代碼即可。然后你的JS可以像使用另一個(gè)JS包或模塊一樣使用Rust了。

wasm-bindgen還能做什么

wasm-bindgen項(xiàng)目在這個(gè)領(lǐng)域內(nèi)志向遠(yuǎn)大,我們?cè)诖瞬辉僭敿?xì)贅述。探索wasm-bindgen中的功能一個(gè)有效的方法就是探索示例目錄,這些示例涵蓋了從我們之前看到的Hello World! 到在Rust中對(duì)DOM節(jié)點(diǎn)的完全操作。

wasm-bindgen高級(jí)特性如下:

  • 引入JS結(jié)構(gòu),函數(shù),對(duì)象等來在wasm中調(diào)用。你可以在一個(gè)結(jié)構(gòu)中調(diào)用JS方法,也可以訪問屬性,這給人一種Rust是“原生”的感覺,讓人覺得你曾經(jīng)寫過的Rust #[wasm_bindgen] annotations都可以連接了起來。

  • 將Rust結(jié)構(gòu)和函數(shù)導(dǎo)出到JS。與只用JS使用數(shù)字類型來工作相比,你可以導(dǎo)出一個(gè)Rust結(jié)構(gòu)并在JS中轉(zhuǎn)換成一個(gè)類。然后可以將結(jié)構(gòu)傳遞,而不是只使用整形數(shù)值來傳遞。 smorgasboard 這個(gè)例子可以讓你體會(huì)支持的互操作特性。

  • 其他各種各樣的特性例如從全局范圍內(nèi)導(dǎo)入(就像alert函數(shù)),在Rust中使用一個(gè)Result來獲取JS異常,以及在Rust程序中通用方法模擬存儲(chǔ)JS值。

如果你想了解更多的功能,繼續(xù)閱讀 issue tracker。

3、wasm-bindgen接下來做什么?

在我們結(jié)束之前,我想花一點(diǎn)時(shí)間來下描述wasm-bindgen的未來愿景,因?yàn)槲艺J(rèn)為這是當(dāng)今項(xiàng)目最激動(dòng)人心的一方面。

不僅僅支持Rust

從第1天起,wasm-bindgen CLI工具就設(shè)計(jì)成了多語言支持的。盡管Rust目前是唯一被支持的語言,但該工具也可以嵌入C或C++。 #[wasm_bindgen]屬性創(chuàng)建了可被wasm-bindgen工具解析并隨后刪除的輸出(* .wasm)文件的自定義部分。

本節(jié)介紹要生成哪些JS綁定以及它們的接口是什么。這個(gè)描述中沒有關(guān)于Rust的特定部分,因此C ++編譯器插件可以很容易地創(chuàng)建該部分,并通過wasm-bindgen工具進(jìn)行處理。

我覺得這個(gè)方面特別令人振奮,因?yàn)槲蚁嘈潘瓜駑asm-bindgen這樣的工具成為WebAssembly和JS集成的標(biāo)準(zhǔn)做法。希望所有編譯為WebAssembly的語言都能受益,并且可以被bundler自動(dòng)識(shí)別,以避免上述幾乎所有的配置和構(gòu)建工具。

自動(dòng)綁定JS生態(tài)

使用#[wasm_bindgen] 宏導(dǎo)入功能唯一不好的一面就是你必須將所有東西都寫出來,還要保證沒有任何錯(cuò)誤。這種讓人覺得很單調(diào)(而且易錯(cuò))的操作的自動(dòng)化技術(shù)已經(jīng)成熟了。

所有的web APIs都由WebIDL指定,而且在generate #[wasm_bindgen] annotations from WebIDL是可行的。這個(gè)就意味著你不需要像前面一樣定義alert函數(shù),而是你只需要寫下面這些:

代碼

#[wasm_bindgen]
pub fn greet(s: &str) {
  webapi::alert(&format!("Hello, {}!", s));
}

在這個(gè)例子中,WebIDL對(duì)web APIs的描述可以完全自動(dòng)生成webapi集合,保證沒有錯(cuò)誤。

我們甚至可以將自動(dòng)化更進(jìn)一步,TypeScript組織已經(jīng)做了這方面的復(fù)雜工作,參照generate #[wasm_bindgen] from TypeScript as well??梢悦赓M(fèi)用npm上的TypeScript自動(dòng)綁定任何包!

比 JS DOM 操作更快的性能

最后要說的事情對(duì) wasm-bindgen 來說也很重要:超快的 DOM 操作 —— 這是很多 JS 框架的終極目標(biāo)。如今需要使用一些中間工具來調(diào)用 DOM 函數(shù),這些工具正在由 JavaScript 實(shí)現(xiàn)轉(zhuǎn)向 C++ 引擎實(shí)現(xiàn)。然而,在 WebAssembly 來臨之后,這些工具并非必須。WebAssembly 是有類型的。

從第一天起,wasm-bindgen 代碼生成的設(shè)計(jì)就考慮到了將來的宿主綁定方案。當(dāng)這一特征出現(xiàn)在 WebAssembly 之后,我們可以直接調(diào)用導(dǎo)入的函數(shù),而不需要 wasm-bindgen 的中間工具。

此外,它使得 JS 引擎積極優(yōu)化 WebAssembly 對(duì) DOM 的操作,使其對(duì)類型的支持更好,而且在調(diào)用 JS 的時(shí)候不再需要進(jìn)行參數(shù)驗(yàn)證。在這一點(diǎn)上,wasm-bindgen 不僅在操作像 string 這樣的富類型變得容易,還提供了一流的 DOM 操作性能。

以上是“JavaScript和Rust中wasm-bindgen組件有什么用”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI