將結構體匯出到 JS
到目前為止,我們已經介紹了 JS 物件、導入函式和匯出函式。這已經給了我們一個相當豐富的基礎,可以建立在此基礎之上,這很棒!但有時我們希望更進一步,在 Rust 中定義一個 JS class
。或者換句話說,我們希望從 Rust 將帶有方法物件公開到 JS,而不僅僅是導入/匯出自由函式。
#[wasm_bindgen]
屬性可以註解 struct
和 impl
區塊以允許
# #![allow(unused_variables)] #fn main() { #[wasm_bindgen] pub struct Foo { internal: i32, } #[wasm_bindgen] impl Foo { #[wasm_bindgen(constructor)] pub fn new(val: i32) -> Foo { Foo { internal: val } } pub fn get(&self) -> i32 { self.internal } pub fn set(&mut self, val: i32) { self.internal = val; } } #}
這是一個典型的 Rust struct
定義,用於具有建構子和一些方法類型。使用 #[wasm_bindgen]
註解結構體表示我們將產生必要的特徵實作,以將此類型轉換為/從 JS 邊界轉換。此處註解的 impl
區塊表示內部函式也將透過產生的 shim 提供給 JS 使用。如果我們看看為此產生的 JS 程式碼,我們會看到
import * as wasm from './js_hello_world_bg';
export class Foo {
static __construct(ptr) {
return new Foo(ptr);
}
constructor(ptr) {
this.ptr = ptr;
}
free() {
const ptr = this.ptr;
this.ptr = 0;
wasm.__wbg_foo_free(ptr);
}
static new(arg0) {
const ret = wasm.foo_new(arg0);
return Foo.__construct(ret)
}
get() {
const ret = wasm.foo_get(this.ptr);
return ret;
}
set(arg0) {
const ret = wasm.foo_set(this.ptr, arg0);
return ret;
}
}
實際上不多!我們可以在此看到我們如何從 Rust 翻譯到 JS
- Rust 中的關聯函式 (不帶
self
的函式) 會變成 JS 中的static
函式。 - Rust 中的方法會變成 wasm 中的方法。
- 手動記憶體管理也在 JS 中公開。必須叫用
free
函式才能釋放 Rust 端資源。
為了能夠使用 new Foo()
,您需要將 new
註解為 #[wasm_bindgen(constructor)]
。
但是,此處要注意的一個重要方面是,一旦呼叫 free
,JS 物件就會被「去勢」,因為它的內部指標會被設為 null。這表示此物件的未來使用應該會觸發 Rust 中的 panic。
這些綁定的真正訣竅最終會出現在 Rust 中,因此讓我們看看這個。
# #![allow(unused_variables)] #fn main() { // original input to `#[wasm_bindgen]` omitted ... #[export_name = "foo_new"] pub extern "C" fn __wasm_bindgen_generated_Foo_new(arg0: i32) -> u32 { let ret = Foo::new(arg0); Box::into_raw(Box::new(WasmRefCell::new(ret))) as u32 } #[export_name = "foo_get"] pub extern "C" fn __wasm_bindgen_generated_Foo_get(me: u32) -> i32 { let me = me as *mut WasmRefCell<Foo>; wasm_bindgen::__rt::assert_not_null(me); let me = unsafe { &*me }; return me.borrow().get(); } #[export_name = "foo_set"] pub extern "C" fn __wasm_bindgen_generated_Foo_set(me: u32, arg1: i32) { let me = me as *mut WasmRefCell<Foo>; wasm_bindgen::__rt::assert_not_null(me); let me = unsafe { &*me }; me.borrow_mut().set(arg1); } #[no_mangle] pub unsafe extern "C" fn __wbindgen_foo_free(me: u32) { let me = me as *mut WasmRefCell<Foo>; wasm_bindgen::__rt::assert_not_null(me); (*me).borrow_mut(); // ensure no active borrows drop(Box::from_raw(me)); } #}
和之前一樣,這是從實際輸出中清除的,但與正在發生的事情的想法相同!在此,我們可以看到每個函式的 shim 以及取消分配 Foo
實例的 shim。回想一下,今天唯一有效的 wasm 類型是數字,因此我們需要將所有的 Foo
都塞入 u32
中,目前是透過 Box
(類似於 C++ 中的 std::unique_ptr
) 來完成的。但請注意,這裡還有額外的一層,WasmRefCell
。此類型與 RefCell
相同,並且可以大致忽略。
如果您有興趣的話,此類型的目的是在別名普遍存在的世界 (JS) 中,維護 Rust 關於別名的保證。具體來說,&Foo
類型表示您可以隨意使用任意數量的別名,但至關重要的是,&mut Foo
表示它是資料的唯一指標 (不存在相同實例的其他 &Foo
)。libstd 中的 RefCell
類型是一種在執行階段動態強制執行此操作的方法 (與通常在編譯階段發生的情況相反)。在此處加入 WasmRefCell
的想法相同,為通常在編譯階段發生的別名加入執行階段檢查。這目前是一個 Rust 特有的功能,它實際上並不在 wasm-bindgen
工具本身中,它只是在 Rust 產生的程式碼中 (又名 #[wasm_bindgen]
屬性)。