从 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 - 这是在 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`。

`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` 的实例旨在表示存储在我们模块的堆栈/slab 中的 JS 对象。然后,它与我们在开头看到的 JS 对象的工作方式基本相同。

当调用 `Bar::new` 时,我们将获得一个索引,该索引被包装在 `Bar` 中(当剥离到最底层时,它本身在内存中只是一个 `u32`)。然后,每个函数将索引作为第一个参数传递,并在 Rust 中转发所有其他内容。