Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#[wasm_bindgen(...)] pragma to convert to plain Object instead of Class #2645

Open
fosskers opened this issue Aug 3, 2021 · 9 comments
Open

Comments

@fosskers
Copy link

fosskers commented Aug 3, 2021

Motivation

APIs like https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/connect expect to be passed Objects with specific fields. The following is currently possible:

#[wasm_bindgen]
struct Foo {
  bar: String,
  baz: bool,
}

And this Foo can be used as an argument to a bound JS function, but it seems that the Foo is encoded as a class, and APIs that do runtime field sanity checking (like the one above) reject anything with any extra structure (fields, etc.) than what they expected.

Proposed Solution

I propose that an option be added to the #[wasm_bindgen] macro, such as #[wasm_bindgen(as_object)], that would result in the implementations of IntoWasmAbi etc. encoding the Rust struct as a plain JS object. This would:

  1. Allow first-class usage of the APIs like the one linked above.
  2. Avoid the need for extra calls to JsValue::from_serde or serde-wasm-bindgen.
  3. Avoid a Result from calling the functions in (2).
  4. Allow the Foo to be pulled back out of a bound JS function as the return value (maybe not possible?)

Alternatives

As mentioned in point (2) above, the current workaround is to leave all arguments to bound JS functions as JsValue and write sugar functions over top of them than work in "Serde Land", first encoding via Serde into a JsValue before passing it down.

Unless, of course, I have completely misunderstood how the existing #[wasm_bindgen] macro works!

@MartinKavik
Copy link
Contributor

Another use case from a real-world app:
Creating a config object for the Youtube JS library currently looks like this:

let events = js_sys::Object::new();
Reflect::set(&events, &"onReady".into(), on_ready.as_ref()).unwrap();

let config = js_sys::Object::new();
Reflect::set(&config, &"width".into(), &"100%".into()).unwrap();
Reflect::set(&config, &"events".into(), &events).unwrap();

This would be much cleaner:

#[wasm_bindgen(as_object)]
struct Events{
    #[wasm_bindgen(js_name = onReady)]
    on_ready: Closure<dyn Fn()>, // or a reference or a `js_sys::Function` if possible
}

#[wasm_bindgen(as_object)]
struct Foo {
    width: String,
    events: Events,
}

@fosskers
Copy link
Author

Hello there, any thoughts on this?

@levrik
Copy link

levrik commented Aug 23, 2021

@fosskers I think what you want is already possible with https://github.com/cloudflare/serde-wasm-bindgen. This crate is also linked from the wasm-bindgen docs. Still would be great to see this supported out-of-the-box.

@fosskers
Copy link
Author

I do use that crate extensively already, although it seems to me that structs should be able to convert into JS objects natively (at least in the IntoWasmAbi direction) without having to go through a serialization library.

@levrik
Copy link

levrik commented Aug 24, 2021

@fosskers Ah, yeah. Makes sense.

@bobby
Copy link

bobby commented Apr 3, 2023

This would be super useful for some of my current use-cases.

@hamza1311
Copy link
Collaborator

https://github.com/hamza1311/ducktor is also another solution for this, though it falls apart when nested complex types are involved

@charlag
Copy link

charlag commented Feb 8, 2024

uniffi makes a useful distinction between structs and objects.

At Tutao we are currently evaluating the possibility of using both uniffi and wasm-bindgen for using the same Rust SDK and this is a major roadblock. There are many cases where we want to pass plain objects into/from API calls. Something like ducktor but that also generates typescript types (without wrapper classes) would be ideal.

It seems odd that something complicated like refcounted classes with prototype hierarchies is implemented but mapping of plain objects is a problem.

@daxpedda
Copy link
Collaborator

daxpedda commented Feb 9, 2024

We are doing this internally already when generating dictionary types from the WebIDL.
I'm happy to review a PR adding #[wasm_bindgen(object)] (subject to bikeshedding).

See #3468 as well, where we want to move away from Reflect for dictionary types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants