將函式匯出至 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 墊片。

大多數引數都有相對明確的轉換方式,但如果您有任何疑問,請告訴我!