將函式匯出至 JS
好的,現在我們已經很好地掌握了 JS 物件及其運作方式,讓我們來看看 `wasm-bindgen` 的另一個功能:匯出具有比數字更豐富的類型功能。
匯出具有更豐富類型功能的基本概念是,Wasm 匯出實際上不會被直接呼叫。相反,產生的 `foo.js` 模組將具有 Wasm 模組中所有匯出函數的墊片。
這裡最有趣的轉換發生在字串上,所以讓我們來看看。
# #![allow(unused_variables)] #fn main() { #[wasm_bindgen] pub fn greet(a: &str) -> String { format!("Hello, {}!", a) } #}
在這裡,我們想定義一個看起來像這樣的 ES 模組
// foo.d.ts
export function greet(a: string): string;
為了了解發生了什麼,讓我們看看產生的墊片
import * as wasm from './foo_bg';
function passStringToWasm(arg) {
const buf = new TextEncoder('utf-8').encode(arg);
const len = buf.length;
const ptr = wasm.__wbindgen_malloc(len, 1);
let array = new Uint8Array(wasm.memory.buffer);
array.set(buf, ptr);
return [ptr, len];
}
function getStringFromWasm(ptr, len) {
const mem = new Uint8Array(wasm.memory.buffer);
const slice = mem.slice(ptr, ptr + len);
const ret = new TextDecoder('utf-8').decode(slice);
return ret;
}
export function greet(arg0) {
const [ptr0, len0] = passStringToWasm(arg0);
try {
const ret = wasm.greet(ptr0, len0);
const ptr = wasm.__wbindgen_boxed_str_ptr(ret);
const len = wasm.__wbindgen_boxed_str_len(ret);
const realRet = getStringFromWasm(ptr, len);
wasm.__wbindgen_boxed_str_free(ret);
return realRet;
} finally {
wasm.__wbindgen_free(ptr0, len0, 1);
}
}
呼,這還真不少!不過,如果我們仔細觀察,就可以大致看出發生了什麼
-
字串透過兩個引數傳遞給 Wasm,一個指標和一個長度。現在我們必須將字串複製到 Wasm 堆積上,這意味著我們將使用 `TextEncoder` 來實際進行編碼。完成此操作後,我們會在 `wasm-bindgen` 中使用內部函數來為字串分配空間,然後稍後將該 ptr/length 傳遞給 Wasm。
-
從 Wasm 傳回字串有點棘手,因為我們需要傳回 ptr/len 對,但 Wasm 目前僅支援一個傳回值(多個傳回值 正在標準化)。為了暫時解決這個問題,我們實際上傳回一個指向 ptr/len 對的指標,然後使用函數來存取各個欄位。
-
一些清理工作最終會在 wasm 中進行。`__wbindgen_boxed_str_free` 函數用於在字串被解碼到 JS 堆積上(使用 `TextDecoder`)之後釋放 `greet` 的傳回值。`__wbindgen_free` 然後用於釋放我們分配用於傳遞字串引數的空間,一旦函數呼叫完成。
接下來,讓我們也看看 Rust 方面的情況。在這裡,我們將看到一個大部分被縮寫和/或「簡化」的版本,因為這是它編譯成的樣子
# #![allow(unused_variables)] #fn main() { pub extern "C" fn greet(a: &str) -> String { format!("Hello, {}!", a) } #[export_name = "greet"] pub extern "C" fn __wasm_bindgen_generated_greet( arg0_ptr: *const u8, arg0_len: usize, ) -> *mut String { let arg0 = unsafe { let slice = ::std::slice::from_raw_parts(arg0_ptr, arg0_len); ::std::str::from_utf8_unchecked(slice) }; let _ret = greet(arg0); Box::into_raw(Box::new(_ret)) } #}
在這裡,我們可以再次看到我們的 `greet` 函數未修改,並且有一個包裝器來呼叫它。這個包裝器會接收 ptr/len 引數並將其轉換為字串切片,而傳回值會被封裝成一個指標,然後透過 `__wbindgen_boxed_str_*` 函數傳回到 wasm 以供讀取。
因此,通常匯出一個函數會涉及 JS 和 Rust 中的墊片,每一方都會在 Wasm 引數與每一種語言的原生類型之間進行轉換。`wasm-bindgen` 工具會管理所有這些墊片的連接,而 `#[wasm_bindgen]` 巨集也會處理 Rust 墊片。
大多數引數都有相對明確的轉換方式,但如果您有任何疑問,請告訴我!