將函式匯出至 JS

好的,現在我們已經對 JS 物件及其運作方式有很好的掌握,讓我們來看看 `wasm-bindgen` 的另一個功能:匯出具有比數字更豐富類型的功能。

使用更豐富的類型匯出功能的基本概念是,Wasm 匯出實際上不會被直接呼叫。相反,產生的 `foo.js` 模組將為 Wasm 模組中的所有匯出函式提供墊片。

這裡最有趣的轉換發生在字串上,所以讓我們來看看。

#![allow(unused)]
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` 中的內部函式為字串分配空間,然後我們稍後會將該指標/長度傳遞給 Wasm。

  • 從 Wasm 返回字串有點棘手,因為我們需要返回一個指標/長度對,但 Wasm 目前僅支援一個返回值(多個返回值 正在標準化中)。為了暫時解決這個問題,我們實際上返回一個指向指標/長度對的指標,然後使用函式來存取各種欄位。

  • 一些清理工作最終在 wasm 中完成。`__wbindgen_boxed_str_free` 函式用於釋放 `greet` 的返回值,該返回值在被解碼到 JS 堆上(使用 `TextDecoder`)後。然後使用 `__wbindgen_free` 來釋放我們分配用於傳遞字串參數的空間,一旦函式呼叫完成。

接下來讓我們看看 Rust 方面的內容。在這裡,我們將看到大部分經過縮寫和/或「簡化」的內容,因為這就是它編譯後的結果

#![allow(unused)]
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` 函式沒有修改,並且有一個包裝器來呼叫它。這個包裝器將採用指標/長度參數並將其轉換為字串切片,而返回值則被打包成一個指標,然後透過 `__wbindgen_boxed_str_*` 函式返回給 wasm 進行讀取。

所以一般來說,匯出函式涉及到 JS 和 Rust 中的墊片,每一側都會將 Wasm 參數轉換為每種語言的原生類型或從其轉換。`wasm-bindgen` 工具管理著連接所有這些墊片,而 `#[wasm_bindgen]` 巨集也處理了 Rust 墊片。

大多數參數都有一個相對清晰的轉換方式,但如果您有任何疑問,請告訴我!