從 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
分派。
get
和 set
函式是方法,所以它們會通過 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 中,我們看到為此類別的導入產生了一個新的型別 Bar
。Bar
型別在內部包含一個 JsValue
,因為 Bar
的實例旨在表示儲存在我們模組堆疊/平板中的 JS 物件。然後,它的運作方式與我們在開始時看到的 JS 物件運作方式大致相同。
當呼叫 Bar::new
時,我們會獲得一個索引,該索引會被包裝在 Bar
中(它本身在被剝離時只是記憶體中的 u32
)。然後,每個函式都會將索引作為第一個引數傳遞,否則會將所有內容轉發到 Rust 中。