Wasm-bindgen: Downcast a JsValue to an exported struct

Created on 9 Jul 2020  路  2Comments  路  Source: rustwasm/wasm-bindgen

Summary

In the code below, how can I cast the JsValue to Foo?

#[wasm_bindgen]
pub struct Foo(u8); // Assume this can't be serialized or deserialized

#[wasm_bindgen]
pub fn foo_downcast(foo: JsValue) {
    let foo: Foo = todo!();
    // ...
}

Additional Details

From<Foo> for JsValue is implemented automatically, so I'd expect something like TryInto<Foo> for JsValue to be implemented too, but I couldn't find anything.

Reasons why you'd want to do this

  1. Passing a vector or slice of objects to an exported function (currently Vec<Foo> and &[Foo] are unsupported, but Vec<JsValue> and &[JsValue] are supported, I think)
  2. Having automatic casting rules for parameters of exported functions (automatically parsing strings if the parameter is a string, etc)
question

Most helpful comment

We've developed a workaround that discovers the runtime class name through .__proto__.constructor.name:

use wasm_bindgen::convert::FromWasmAbi;
pub fn generic_of_jsval<T: FromWasmAbi<Abi=u32>>(js: JsValue, classname: &str) -> Result<T, JsValue> {
    use js_sys::{Object, Reflect};
    let ctor_name = Object::get_prototype_of(&js).constructor().name();
    if ctor_name == classname {
        let ptr = Reflect::get(&js, &JsValue::from_str("ptr"))?;
        let ptr_u32: u32 = ptr.as_f64().ok_or(JsValue::NULL)? as u32;
        let foo = unsafe { T::from_abi(ptr_u32) };
        Ok(foo)
    } else {
        Err(JsValue::NULL)
    }
}
#[wasm_bindgen]
pub fn foo_of_jsval(js: JsValue) -> Option<Foo> {
    generic_of_jsval(js, "Foo").unwrap_or(None)
}

Currently, "Foo" needs to be hard-coded in the snippet since the #[wasm_bindgen] derive macro doesn't generate a way to refer to it programatically. It looks like the ToTokens impl for ast::Struct (https://github.com/rustwasm/wasm-bindgen/blob/17950202ca9458d35bd78a48ebb126800edb0999/crates/backend/src/codegen.rs#L139) has a js_name that could be added. Would adding another trait to wasm_bindgen::convert that exposes this metadata and generating it from the derive macro be the preferred way to do this?

Also, should the method that performs the downcast be added to wasm_bindgen itself (possibly to the same wasm_bindgen::convert trait, if we're adding a new one), or to a different component like js_sys?

All 2 comments

Ah yeah currently this isn't implemented. It would require an intrinsic of one form or another since the JS class lives in the JS file to test inheritance. This would be a nice feature to have though!

We've developed a workaround that discovers the runtime class name through .__proto__.constructor.name:

use wasm_bindgen::convert::FromWasmAbi;
pub fn generic_of_jsval<T: FromWasmAbi<Abi=u32>>(js: JsValue, classname: &str) -> Result<T, JsValue> {
    use js_sys::{Object, Reflect};
    let ctor_name = Object::get_prototype_of(&js).constructor().name();
    if ctor_name == classname {
        let ptr = Reflect::get(&js, &JsValue::from_str("ptr"))?;
        let ptr_u32: u32 = ptr.as_f64().ok_or(JsValue::NULL)? as u32;
        let foo = unsafe { T::from_abi(ptr_u32) };
        Ok(foo)
    } else {
        Err(JsValue::NULL)
    }
}
#[wasm_bindgen]
pub fn foo_of_jsval(js: JsValue) -> Option<Foo> {
    generic_of_jsval(js, "Foo").unwrap_or(None)
}

Currently, "Foo" needs to be hard-coded in the snippet since the #[wasm_bindgen] derive macro doesn't generate a way to refer to it programatically. It looks like the ToTokens impl for ast::Struct (https://github.com/rustwasm/wasm-bindgen/blob/17950202ca9458d35bd78a48ebb126800edb0999/crates/backend/src/codegen.rs#L139) has a js_name that could be added. Would adding another trait to wasm_bindgen::convert that exposes this metadata and generating it from the derive macro be the preferred way to do this?

Also, should the method that performs the downcast be added to wasm_bindgen itself (possibly to the same wasm_bindgen::convert trait, if we're adding a new one), or to a different component like js_sys?

Was this page helpful?
0 / 5 - 0 ratings