Attribute Macro wasm_export

Source
#[wasm_export]
Expand description

A proc macro that generates the wasm function bindings with WasmEncodedResult return type from rust functions that natively return Result<T, E>, this makes it possible to avoid throwing on js when return value is Result::Err variant, instead it will return WasmEncodedResult<T> normally on js where either of Result::Ok or Result::Err variants are included within the WasmEncodedResult properties.

All of the wasm_bindgen attributes are available for this macro and are forwarded directly to wasm_bindgen macro on expansion.

Example:

use wasm_bindgen_utils::prelude::*;

struct TestStruct;

#[wasm_export]
impl TestStruct {
    #[wasm_export(js_name = "someStaticMethod", unchecked_return_type = "string")]
    pub async fn some_static_method((arg1, arg2): (String, u8)) -> Result<String, Error> {
        Ok(String::new())
    }
    #[wasm_export(skip)]
    pub async fn some_skip_fn() -> Result<String, Error> {
        Ok(String::new())
    }
    #[some_external_macro]
    #[wasm_export(some_other_wbg_attrs)]
    pub fn some_self_method(&self, arg: String) -> Result<TestStruct, Error> {
        Ok(TestStruct)
    }
}

above will basically translate to the following:

impl TestStruct {
    pub async fn some_static_method((arg1, arg2): (String, u8)) -> Result<String, Error> {
        Ok(String::new())
    }
    pub async fn some_skip_fn() -> Result<String, Error> {
        Ok(String::new())
    }
    #[some_external_macro]
    pub fn some_self_method(&self, arg: String) -> Result<TestStruct, Error> {
        Ok(TestStruct)
    }
}
#[wasm_bindgen]
impl TestStruct {
    #[wasm_bindgen(js_name = "someStaticMethod", unchecked_return_type = "WasmEncodedResult<string>")]
    pub async fn some_static_method__wasm_export((arg1, arg2): (String, u8)) -> WasmEncodedResult<String> {
        Self::some_static_method((arg1, arg2)).await.into()
    }
    #[wasm_bindgen(some_other_wbg_attrs, unchecked_return_type = "WasmEncodedResult<TestStruct>")]
    pub fn some_self_method__wasm_export(&self, arg: String) -> WasmEncodedResult<TestStruct> {
        self.some_self_method(arg).into()
    }
}

§Preserving JS Class In WasmEncodedResult

By default, WasmEncodedResult is serialized to JS/TS using serde_wasm_bindgen due to wasm_bindgen’s limitations with generic types, this is fine when the Ok variant is not a JS/TS class instance, however, this approach does not preserve JS/TS class instances if a Rust struct corresponds to one, but rather serde_wasm_bindgen serialization will convert it to a plain object, similar to JSON stringification.

To address this, you can use the preserve_js_class attribute on a method or function; when enabled, the macro (using js_sys lib helpers) bypasses serde_wasm_bindgen serialization and manually populates the value and error fields of an empty JS object to resemble WasmEncodedResult, ensuring that class instances are preserved in JS/TS as expected. As a result, the macro generated exporting function in Rust has to return a JsValue rather than WasmEncodedResult<T>, but it is typed with unchecked_return_type = "WasmEncodedResult<T>" (this is just a technicality of it, it doesn’t affect the users of the macro)

example: in rust we will use it like:

#[wasm_bindgen]
struct TestStruct;

#[wasm_export]
impl TestStruct {
    #[wasm_export(js_name = "new", preserve_js_class)]
    pub fn new() -> Result<TestStruct, Error> {
        Ok(TestStruct)
    }
}

and we will get the following on JS/TS:

const result = TestStruct.new();
if (result.error) {
    // handle error
} else {
    const testStruct = result.value;
    assert(
        testStruct instanceof TestStruct,
        "Expected to be instance of TestStruct, but it is not"
    );
    // do stuff
}