從 JS 導入類別

就像我們開始匯出函式一樣,我們也會想要導入!既然我們已經將一個 class 匯出到 JS,我們也會希望能夠在 Rust 中導入類別,以便調用方法等等。由於 JS 類別通常只是 JS 物件,此處的綁定看起來會與上述的 JS 物件綁定非常相似。

但像往常一樣,讓我們深入一個範例!


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen(module = "./bar")]
extern "C" {
    type Bar;

    #[wasm_bindgen(constructor)]
    fn new(arg: i32) -> Bar;

    #[wasm_bindgen(js_namespace = Bar)]
    fn another_function() -> i32;

    #[wasm_bindgen(method)]
    fn get(this: &Bar) -> i32;

    #[wasm_bindgen(method)]
    fn set(this: &Bar, val: i32);

    #[wasm_bindgen(method, getter)]
    fn property(this: &Bar) -> i32;

    #[wasm_bindgen(method, setter)]
    fn set_property(this: &Bar, val: i32);
}

fn run() {
    let bar = Bar::new(Bar::another_function());
    let x = bar.get();
    bar.set(x + 3);

    bar.set_property(bar.property() + 6);
}
#}

與我們之前的導入不同,這個導入比較冗長!請記住,wasm-bindgen 的目標之一是盡可能使用原生 Rust 語法,所以這主要是要使用 #[wasm_bindgen] 屬性來解釋在 Rust 中寫下的內容。現在這裡有一些屬性註解,所以讓我們逐一檢視。

  • #[wasm_bindgen(module = "./bar")] - 之前在導入中看過,這宣告了所有後續功能都從哪裡導入。例如,Bar 型別將從 ./bar 模組導入。
  • type Bar - 這是將 JS 類別宣告為 Rust 中的新類型。這表示會產生一個新的 Bar 型別,它是「不透明的」,但在內部表示為包含一個 JsValue。我們稍後會看到更多關於此的內容。
  • #[wasm_bindgen(constructor)] - 這表示綁定的名稱實際上不會在 JS 中使用,而是轉換為 new Bar()。此函式的回傳值必須是一個裸型別,例如 Bar
  • #[wasm_bindgen(js_namespace = Bar)] - 此屬性表示函式宣告在 JS 中通過 Bar 類別進行命名空間化。
  • #[wasm_bindgen(static_method_of = SomeJsClass)] - 此屬性類似於 js_namespace,但它不是產生一個自由函式,而是產生 SomeJsClass 的靜態方法。
  • #[wasm_bindgen(method)] - 最後,此屬性表示將發生方法呼叫。第一個引數必須是 JS 結構,例如 Bar,而 JS 中的呼叫看起來像 Bar.prototype.set.call(...)

考慮到這一切,讓我們看看產生的 JS。

import * as wasm from './foo_bg';

import { Bar } from './bar';

// other support functions omitted...

export function __wbg_s_Bar_new() {
  return addHeapObject(new Bar());
}

const another_function_shim = Bar.another_function;
export function __wbg_s_Bar_another_function() {
  return another_function_shim();
}

const get_shim = Bar.prototype.get;
export function __wbg_s_Bar_get(ptr) {
  return shim.call(getObject(ptr));
}

const set_shim = Bar.prototype.set;
export function __wbg_s_Bar_set(ptr, arg0) {
  set_shim.call(getObject(ptr), arg0)
}

const property_shim = Object.getOwnPropertyDescriptor(Bar.prototype, 'property').get;
export function __wbg_s_Bar_property(ptr) {
  return property_shim.call(getObject(ptr));
}

const set_property_shim = Object.getOwnPropertyDescriptor(Bar.prototype, 'property').set;
export function __wbg_s_Bar_set_property(ptr, arg0) {
  set_property_shim.call(getObject(ptr), arg0)
}

就像從 JS 導入函式時一樣,我們可以看到為所有相關函式產生了一堆墊片。new 靜態函式具有 #[wasm_bindgen(constructor)] 屬性,這表示它不應該調用任何特定方法,而是應該調用 new 建構函式(如我們在此處看到的那樣)。但是,靜態函式 another_function 會作為 Bar.another_function 分派。

getset 函式是方法,所以它們會通過 Bar.prototype,否則它們的第一個引數會隱式地是 JS 物件本身,該物件會像我們之前看到的那樣通過 getObject 載入。

一些真正的實質內容開始在 Rust 端顯示出來,所以讓我們看看


# #![allow(unused_variables)]
#fn main() {
pub struct Bar {
    obj: JsValue,
}

impl Bar {
    fn new() -> Bar {
        extern "C" {
            fn __wbg_s_Bar_new() -> u32;
        }
        unsafe {
            let ret = __wbg_s_Bar_new();
            Bar { obj: JsValue::__from_idx(ret) }
        }
    }

    fn another_function() -> i32 {
        extern "C" {
            fn __wbg_s_Bar_another_function() -> i32;
        }
        unsafe {
            __wbg_s_Bar_another_function()
        }
    }

    fn get(&self) -> i32 {
        extern "C" {
            fn __wbg_s_Bar_get(ptr: u32) -> i32;
        }
        unsafe {
            let ptr = self.obj.__get_idx();
            let ret = __wbg_s_Bar_get(ptr);
            return ret
        }
    }

    fn set(&self, val: i32) {
        extern "C" {
            fn __wbg_s_Bar_set(ptr: u32, val: i32);
        }
        unsafe {
            let ptr = self.obj.__get_idx();
            __wbg_s_Bar_set(ptr, val);
        }
    }

    fn property(&self) -> i32 {
        extern "C" {
            fn __wbg_s_Bar_property(ptr: u32) -> i32;
        }
        unsafe {
            let ptr = self.obj.__get_idx();
            let ret = __wbg_s_Bar_property(ptr);
            return ret
        }
    }

    fn set_property(&self, val: i32) {
        extern "C" {
            fn __wbg_s_Bar_set_property(ptr: u32, val: i32);
        }
        unsafe {
            let ptr = self.obj.__get_idx();
            __wbg_s_Bar_set_property(ptr, val);
        }
    }
}

impl WasmBoundary for Bar {
    // ...
}

impl ToRefWasmBoundary for Bar {
    // ...
}
#}

在 Rust 中,我們看到為此類別的導入產生了一個新的型別 BarBar 型別在內部包含一個 JsValue,因為 Bar 的實例旨在表示儲存在我們模組堆疊/平板中的 JS 物件。然後,它的運作方式與我們在開始時看到的 JS 物件運作方式大致相同。

當呼叫 Bar::new 時,我們會獲得一個索引,該索引會被包裝在 Bar 中(它本身在被剝離時只是記憶體中的 u32)。然後,每個函式都會將索引作為第一個引數傳遞,否則會將所有內容轉發到 Rust 中。