Given a js_sys::Function which is a JS closure returning an instance of an exported Rust struct SomeObject, how can I get back the returned instance ?
Rust:
#[wasm_bindgen]
pub struct SomeObject {}
#[wasm_bindgen]
impl SomeObject {
#[wasm_bindgen(constructor)]
pub fn new() -> SomeObject {
SomeObject {}
}
}
#[wasm_bindgen]
pub fn do_call(f: js_sys::Function) -> Result<(), JsValue> {
// f returns an instance of SomeObject
let ret: JsValue = f.call0(&JsValue::NULL)?;
// XXX how to try_cast ret to its original SomeObject?
// do something with SomeObject's instance
Ok(())
}
JS:
wasm.do_call(function() {
return new wasm.SomeObject();
});
I read the related issue https://github.com/rustwasm/wasm-bindgen/issues/1509 and quoting https://github.com/rustwasm/wasm-bindgen/issues/1509#issuecomment-489215237 :
[...] the only way currently to unwrap it is to call a by-value-
selfmethod on the JS object itself, which will do the unwrapping. [...]
However I don't know how to apply it in this situation.
I'm newbie therefore I may have missed something. Do you have any suggestions?
Thanks,
Gawen
I think it's reasonable for Rust structs to have an impl From<JsValue> for Foo implementation (which already exists for other types, but not Rust structs).
I also have this problem. I've found the following workaround, which involves wrapping the callback function in an object, and telling rust to treat it as a method on a duck-typed interface:
#[wasm_bindgen]
pub struct SomeObject {}
#[wasm_bindgen]
impl SomeObject {
#[wasm_bindgen(constructor)]
pub fn new() -> SomeObject {
SomeObject {}
}
}
#[wasm_bindgen]
extern "C" {
pub type ExecReturnsSomeObject;
#[wasm_bindgen(structural, method)]
pub fn exec(this: &ExecReturnsSomeObject) -> SomeObject;
}
#[wasm_bindgen]
pub fn do_call(f: &ExecReturnsSomeObject) -> Result<(), JsValue> {
// f.exec() returns an instance of SomeObject
let ret: SomeObject = f.exec();
Ok(())
}
wasm.do_call({
exec: function() {
return new wasm.SomeObject();
}
});
I believe the workaround suggested by https://github.com/rustwasm/wasm-bindgen/issues/1509#issuecomment-489215237 would look something like this (though I couldn't make the unwrap method take this by value, only by reference). It also uses some wrapping, but unlike the previous workaround, it wraps the return value, not the callback function:
#[wasm_bindgen]
pub struct SomeObject {}
#[wasm_bindgen]
impl SomeObject {
#[wasm_bindgen(constructor)]
pub fn new() -> SomeObject {
SomeObject {}
}
}
#[wasm_bindgen]
extern "C" {
pub type WrapperForSomeObject;
#[wasm_bindgen(structural, method)]
pub fn get_inner(this: &WrapperForSomeObject) -> SomeObject;
}
#[wasm_bindgen]
pub fn do_call(f: &js_sys::Function) -> Result<(), JsValue> {
use wasm_bindgen::JsCast;
// f returns an instance of WrapperForSomeObject
let ret: JsValue = f.call0(&JsValue::NULL)?;
let obj: SomeObject = ret.dyn_into::<WrapperForSomeObject>()?.get_inner();
Ok(())
}
class WrapperForSomeObject {
constructor(obj) {
this.obj = obj;
}
get_inner() {
return this.obj;
}
}
// ...
wasm.do_call(function() {
const result = new wasm.SomeObject();
return new WrapperForSomeObject(result);
});
@mstange thanks! your second workaround is neat. I'll use this pattern in the meantime.
Thanks for the report @Gawen!
Currently this isn't directly supported but @Pauan is right in that we should have this! I don't think we'll be able to use From but what we can probably do is something like:
impl TryFrom<JsValue> for RustType {
type Error = JsValue;
// ...
}
internally that would do a check that JsValue has the right class in JS, it'd extract the ptr value, and then unwrap the value in Rust.
This is a relatively meaty addition to wasm-bindgen, and if someone's curious to try it out it may be a good opportunity to learn about the macro and intrinsics! The general structure of this would look like:
crates/backend/src/codegen.rs) would be updated to have the TryFrom impl (or some similar method)fn #the_name(js: u32) -> *mut #the_rust_type;. If a non-null pointer is returned we'd then do the unwrapping process for the type (get it out of the RefCell and such), otherwise the original JsValue is returned.crates/cli-support in the same way instanceof wrappers and such are already recognized.A impl From<JsValue> for RustStruct implementation would also help interoperability with JS promises: If I have a duck-typed reference to a JS object in my Rust code, and that JS object has a method that returns a promise to a Rust struct, the Rust code cannot get the struct out of the promise. It can only get a JsValue out of the promise.
Luckily, the wrapper object workaround (see above) works for this use case, too.
#[wasm_bindgen]
pub struct WasmMemBuffer {}
#[wasm_bindgen]
extern "C" {
pub type FileAndPathHelper;
/// Returns Promise of BufferWrapper
/// (but wants to return Promise of WasmMemBuffer)
#[wasm_bindgen(method)]
fn readFile(this: &FileAndPathHelper, path: &str) -> Promise;
pub type BufferWrapper;
#[wasm_bindgen(method)]
fn getBuffer(this: &BufferWrapper) -> WasmMemBuffer;
}
Don't mind me, just adding some search terms for this issue:
convert, cast
Most helpful comment
Thanks for the report @Gawen!
Currently this isn't directly supported but @Pauan is right in that we should have this! I don't think we'll be able to use
Frombut what we can probably do is something like:internally that would do a check that
JsValuehas the right class in JS, it'd extract theptrvalue, and then unwrap the value in Rust.