天天酷跑小蜜桃|天天酷跑女角色被日
閱讀更多
引用
原文:JavaScript to Rust and Back Again: A wasm-bindgen Tale
鏈接:https://hacks.mozilla.org/2018/04/javascript-to-rust-and-back-again-a-wasm-bindgen-tale/
譯者:Tocy, 琪花億草, 雪落無痕xdj, 邊城

最近我們已經見識了WebAssembly如何快速編譯、加速JS庫以及生成更小的二進制格式。我們甚至為Rust和JavaScript社區以及其他Web編程語言之間的更好的互操作性制定了高級規劃。正如前面一篇文章中提到的,我想深入了解一個特定組件的細節,wasm-bindgen。

今天WebAssembly標準只定義了四種類型:兩種整數類型和兩種浮點類型。然而,大多數情況下,JS和Rust開發人員正在使用更豐富的類型! 例如,JS開發人員經常與互以添加或修改HTML節點相關的文檔交互,而Rust開發人員使用類似Result等類型進行錯誤處理,幾乎所有程序員都使用字符串。


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


wasm-bindgen的目標是提供一個JS和Rust類型之間的橋接。它允許JS使用字符串調用Rust API,或Rust函數捕獲JS異常。wasm-bindgen抹平了WebAssembly和JavaScript之間的阻抗失配,確保JavaScript可以高效地調用WebAssembly函數,并且無需boilerplate,同時WebAssembly可以對JavaScript函數執行相同的操作。

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

1、Hello World!

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

這里的目標很簡單,我們想要定義一個Rust的函數,給定一個名字,它會在頁面上創建一個對話框,上面寫著Hello,$name!在JavaScript中,我們可以將這個函數定義為:
export function greet(name) {
    alert(`Hello, ${name}!`);
}

不過在這個例子里要注意的是,我們將把它用Rust編寫。這里已經發生了很多我們必須要處理的事情:
  • JavaScript將會調用一個WebAssembly 模塊, 模塊名是 greetexport.
  • Rust函數將一個字符串作為輸入參數,也就是我們要打招呼的名字。
  • 在內部Rust會生成一個新的字符串,也就是傳入的名字。
  • 最后Rust會調用JavaScript的 alert函數,以剛創建的字符串作為參數。
啟動第一步,我們創建一個新的Rust工程:
$ cargo new wasm-greet --lib

這將初始化一個新的wasm-greet文件夾,我們的工作都在這里面完成。接下來我們要使用如下信息修改我們的Cargo.toml(在Rust里相當于package.json):
[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

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

接下來,是時候編寫一些代碼了!我們使用下列內容替換了自動創建的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,這可能看起來有點啰嗦,但不要害怕!隨著時間的推移,wasm-bindgen項目不斷改進,而且可以肯定的是,所有這些并不總是必要的。

要注意的最重要的一點是#[wasm_bindgen]屬性,這是一個在Rust代碼中的注釋,這里的意思是“請在必要時用wrapper處理這個”。我們對alert函數的導入和greet函數的導出都被標注為這個屬性。稍后,我們將看到在引擎蓋下發生了什么。

首先,我們從在瀏覽器中打開作為例子來切入正題!我們先編譯wasm代碼:
$ rustup target add wasm32-unknown-unknown --toolchain nightly # only needed once
$ cargo +nightly build --target wasm32-unknown-unknown

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

結果發現這個wasm文件實際上還不能直接被JS調用!為了能讓我們使用,我們需要執行一個或更多步驟:
$ cargo install wasm-bindgen-cli # only needed once
$ wasm-bindgen target/wasm32-unknown-unknown/debug/wasm_greet.wasm --out-dir .

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

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

最終我們接下來要做的是使用bundler對其打包,然后創建一個HTML頁面運行我們的代碼。

在寫這篇文章的時候,只有Webpack’s 4.0 release對WebAssembly的使用有足夠的支持(盡管暫時已經有了 Chrome caveat)。

總有一天,更多的bundler也會接著支持WebAssmbly。在這我不再描述細節,但是你可以看一下在Github倉庫里的example配置。不過如果我們看內容,這個頁面中我們的JS在看起來是這樣的:
const rust = import("./wasm_greet");
rust.then(m => m.greet("World!"));

…就是這些了!現在打開我們的網頁就會顯示一個不錯的“Hello, World!”對話框,這就是Rust驅動的。

2、wasm-bindgen是如何工作的
唷,那是一個巨大的“Hello, World!”。讓我們深入了解一下更多的細節,以了解后臺發生了什么以及該工具是如何工作的。

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

WebAssembly無法在本地執行此操作(請記住,它目前只支持數字),所以我們依靠wasm-bindgen來填補空白。

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

我們可以通過引入 ./wasm_greet 模塊得到 Rust 代碼愿意暴露出來的東西。我們已經看到了是如何集成的,可以繼續看看執行的結果如何。首先是我們的示例:
const rust = import("./wasm_greet");
rust.then(m => m.greet("World!"));

我們在這里以異步的方式導入接口,等待導入完成(下載和編譯 wasm)。然后調用模塊的 greet 函數。

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

如果我們看看由 wasm-bindgen 工具為 wasm_greet.js 文件生成的內容,會看到像這樣的代碼:
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) {
    // ...
}

注: 記住這是生成的,未經優化的代碼,它可能既不優雅也不簡潔!!在 Rust 中通過 LTO(Link Time Optimization,連接時優化)創建新的發行版,再通過 JS 打包工具流程(壓縮)之后,可能會精簡一些。

現在可以了解如何使用wasm-bindgen來生成greet函數。在底層它仍然調用wasm的greet函數,但是它是用一個指針和長度來調用的而不是用字符串。

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

進入更深一層,下一個我們感興趣的就是WebAssmbly中的greet函數。為了了解這個,我們先來看Rust編譯器能訪問到的代碼。注意像上面生成的這種JS wrapper,在這里你不用寫greet的導出符號,#[wasm_bindgen]屬性會生成一個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);
}

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

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

現在我們來看wrappers的最后一塊,即alert函數。Rust中的greet函數使用標準format!宏來創建一個新的字符串然后傳給alert。回想當我們聲明alert方法的時候,我們是使用 #[wasm_bindgen]聲明的,現在我們看看在這個函數中暴露給rustc的內容:
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函數事實上是一個簡化的wrapper,它帶有Rust的 &str然后將它轉換為wasm類型(數字)。它調用了我們在上面看到過的比較有意思的函數__wbg_f_alert_alert_n,然而它奇怪的一點就是#[wasm_import_module]屬性。

在WebAssembly中所有導入的函數都有一個其存在的模塊,而且由于wasm-bindgen構建在ES模塊之上,所以這也將被轉譯為ES模塊導入!

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

最后,對于最后一部分的疑惑,我們得到了我們所生成的JS文件,其中包含:
export function __wbg_f_alert_alert_n(ptr0, len0) {
    let arg0 = getStringFromWasm(ptr0, len0);
    alert(arg0)
}

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

wasm-bindgen還能做什么

wasm-bindgen項目在這個領域內志向遠大,我們在此不再詳細贅述。探索wasm-bindgen中的功能一個有效的方法就是探索示例目錄,這些示例涵蓋了從我們之前看到的Hello World! 到在Rust中對DOM節點的完全操作。

wasm-bindgen高級特性如下:

  • 引入JS結構,函數,對象等來在wasm中調用。你可以在一個結構中調用JS方法,也可以訪問屬性,這給人一種Rust是“原生”的感覺,讓人覺得你曾經寫過的Rust #[wasm_bindgen] annotations都可以連接了起來。
  • 將Rust結構和函數導出到JS。與只用JS使用數字類型來工作相比,你可以導出一個Rust結構并在JS中轉換成一個類。然后可以將結構傳遞,而不是只使用整形數值來傳遞。 smorgasboard 這個例子可以讓你體會支持的互操作特性。
  • 其他各種各樣的特性例如從全局范圍內導入(就像alert函數),在Rust中使用一個Result來獲取JS異常,以及在Rust程序中通用方法模擬存儲JS值。
如果你想了解更多的功能,繼續閱讀 issue tracker

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

在我們結束之前,我想花一點時間來下描述wasm-bindgen的未來愿景,因為我認為這是當今項目最激動人心的一方面。

不僅僅支持Rust

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

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

我覺得這個方面特別令人振奮,因為我相信它使像wasm-bindgen這樣的工具成為WebAssembly和JS集成的標準做法。希望所有編譯為WebAssembly的語言都能受益,并且可以被bundler自動識別,以避免上述幾乎所有的配置和構建工具。

自動綁定JS生態

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

所有的web APIs都由WebIDL指定,而且在generate #[wasm_bindgen] annotations from WebIDL是可行的。這個就意味著你不需要像前面一樣定義alert函數,而是你只需要寫下面這些:
#[wasm_bindgen]
pub fn greet(s: &str) {
    webapi::alert(&format!("Hello, {}!", s));
}

在這個例子中,WebIDL對web APIs的描述可以完全自動生成webapi集合,保證沒有錯誤。

我們甚至可以將自動化更進一步,TypeScript組織已經做了這方面的復雜工作,參照generate #[wasm_bindgen] from TypeScript as well。可以免費用npm上的TypeScript自動綁定任何包!

比 JS DOM 操作更快的性能

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

從第一天起,wasm-bindgen 代碼生成的設計就考慮到了將來的宿主綁定方案。當這一特征出現在 WebAssembly 之后,我們可以直接調用導入的函數,而不需要 wasm-bindgen 的中間工具。

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

收工

我自己發現使用WebAssembly是異常令人振奮的,不僅僅是因為其社區,還因為其如此快速地在進度上突飛猛進。wasm-bindgen工具擁有光明的未來。它使JS和諸如Rust這樣的編程語言之間的互操作性變成了一流的體驗,并且隨著WebAssembly的不斷發展它也將提供了長期的好處。

試著給wasm-bindgen一次機會,因功能需求而創建一個問題,亦或繼續保持參與Rust和WebAssembly!

關于Alex Crichton(作者)
Alex是Rust核心團隊的成員之一,自2012年底以來一直從事于Rust。目前他正在幫助WebAssembly Rust Working Group使得Rust + Wasm成為最佳體驗。Alex還幫助維護Cargo(Rust的包管理器),Rust標準庫以及Rust的發布和CI的基礎架構。
來自: oschina
0
0
評論 共 1 條 請登錄后發表評論
1 樓 xxbb77 2018-11-12 16:32
非常感謝 謝謝分享

發表評論

您還沒有登錄,請您登錄后再發表評論

相關推薦

  • 深入認識javascript中得eval函數

    深入認識javascript中得eval函數深入認識javascript中得eval函數深入認識javascript中得eval函數

  • 深入淺出Rust

    深入淺出Rust,一本介紹Rust語言的好書,Mozilla 公司的最新語言

  • 全方位深入理解JavaScript面向對象

    JavaScript面向對象程序設計 本文會碰到的知識點: 原型、原型鏈、函數對象、普通對象、繼承 讀完本文,可以學到 面向對象的基本概念 JavaScript對象屬性 理解JavaScript中的函數對象與普通對象 理解prototype和proto 理解原型和原型鏈 詳解原型鏈相關的Object方法 了解如何用ES5模擬類,以及各種方式的優缺點 了解如何用ES6實現面向對象 目錄...

  • 深入認識CPU

    學習匯編的時候,對cpu的認識有助于對匯編語言的理解,因為匯編語言功能可以直接對cpu中的寄存器進行操作。 以8086cpu為例子,8086cpu是16位的,那么16位cpu具有哪些性質呢? 運算器一次最多可以處理16位的數據 寄存器的最大寬度為16位 寄存器和運算器之間的通路為16位 1.cpu組成 控制部件單元(Control unit):主要是負責對指令,并且發出為完成每條指令所要執行的各個

  • Rust 程序設計語言(第二版).pdf

    Rust 程序設計語言(第二版).pdf 學習rust必備,rust是mozilla開發,rust的作者也是javascript的作者。

  • 打敗 Python、JS、C# 成最受歡迎編程語言,是時候掌握 Rust 了嗎?

  • 互操作性(筆記)

  • 對html,css,和JavaScript的粗淺認識

    1.css是層疊樣式表,負責給文檔描述樣式; ?2.css就是給html穿衣服的,我們把網頁視為人,html就是身體,css就是衣服,JavaScript就是言行舉止;

  • Rust: 億元估值AI網紅代碼的不同版本

    Rust版本 use std::io; /* AI 核心代碼 --rust 版本 估值 1個億 */ fn main() { loop { let mut str_in = String::new(); io::stdin() .read_line(&mut str_in) .ok() ...

  • IOT語義互操作性之語義

    這個系列文章描述了一個單一的語義數據模型來支持物聯網和建筑、企業和消費者的數據轉換。 這種模型必須簡單可擴展, 以便能夠在各行業領域之間實現插件化和互操作性。 對于一個目前從事智能硬件的老碼農,覺得這...

  • 隨便談談Rust錯誤處理

    錯誤處理? 其實我一直不能太分清楚什么是錯誤什么是異常,不過我倒是覺得區分這些個東西意義不大,重要的是認清本質。一個程序在運行過程中總會碰到一些錯誤,有的是因為用戶的不當操作,有的是因為期望的結果沒有發生,當然還有直接就是無法恢復的程序bug,無論如何,這些問題,如果我們考慮到了,都是需要一套解決方案的,所以我們就來稍微談談這些解決方法,還有我最近學到Rust的解決方法。 從Error Cod...

  • rust 網絡爬蟲相關收集

    Hyper :一個快速和正確的 Rust HTTP實現。 https://github.com/hyperium/hyper html5ever:Rust html解析庫 https://github.com/servo/html5ever 附:https://github.com/carllerche/curl-rust ...

  • 計算機各部件認識

  • 深入理解Tomcat

    簡介tomcat是一個web服務器,運行jsp和servlet,使用HTTP與客戶端(通常是瀏覽器)進行通信。構成下圖是tomcat的架構,可以看出:核心內容是Connector和Container組件。一個Server服務器中可能有多個Service,Service可以暫時理解為“服務”。Server負責管理Service的生命周期,讓外界能夠訪問。Service將Connector和Contai

  • 對JDK的深入理解

  • TCP/UDP深入理解

  • tigase深入理解xmpp服務器-概念總覽

    Tigase是基于組件,插件,連接器實現的框架,Tigase提供了及其靈活的接口,供我們實現自定義組件,插件,連接器。1、組件:是側重實現功能的,例如發布訂閱,Socks5代理,開發者可以為組件配置獨立的數據庫,統計信息等,具體可見開發文檔。組件是Tigase服務器的主要元素。組件是一段更大的代碼,可以具有單獨的地址,接收和發送節,并配置為響應眾多事件。為Tigase服務器實現的示例組件包括:c2...

  • IOT語義互操作性之本體論

    這個系列文章描述了一個單一的語義數據模型來支持物聯網和建筑、企業和消費者的數據轉換。 這種模型必須簡單可擴展, 以便能夠在各行業領域之間實現插件化和互操作性。 對于一個目前從事智能硬件的老碼農,覺得這...

  • 2019 年,Rust 與 WebAssembly 將讓 Web 開發更美好

    作者 |Nick Fitzgerald譯者 |彎月責編 | 屠敏出品 |CSDN(ID:CSDNNews)將 Rust 編譯成 WebAssembly 應該是快速且...

  • rust 的內存管理

Global site tag (gtag.js) - Google Analytics 天天酷跑小蜜桃