從 JS 匯入類別

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

不過,像往常一樣,讓我們深入研究一個範例!

#![allow(unused)]
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 - 這是在 Rust 中宣告 JS 類別為新類型。這表示會產生一個新的 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)]
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 中,我們看到為這個類別匯入產生了一個新的類型 Bar。類型 Bar 內部包含一個 JsValue,因為 Bar 的實例旨在表示儲存在我們模組堆疊/slab 中的 JS 物件。然後,這會以我們在開始時看到 JS 物件工作方式的相同方式運作。

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