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

Trait From<...> is not implemented for JsValue when returning structs in async functions #24

Open
jvanmalder opened this issue Jun 1, 2023 · 2 comments

Comments

@jvanmalder
Copy link

jvanmalder commented Jun 1, 2023

Click to show Cargo.toml.
[package]
name = "asynctest"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
tsify = "0.4.5"
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = { version = "0.2" }
wasm-bindgen-futures = "0.4"

[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"

Using the following code, as in the example in the README, but when using an async function to return a struct:

use serde::{Deserialize, Serialize};
use tsify::Tsify;
use wasm_bindgen::prelude::*;

#[derive(Tsify, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct Point {
    x: i32,
    y: i32,
}

#[wasm_bindgen]
pub async fn into_js() -> Point {
    Point { x: 0, y: 0 }
}

#[wasm_bindgen]
pub fn from_js(point: Point) {}

The compiler complains when executing wasm-pack build --target web --release:

[INFO]: Checking for the Wasm target...
[INFO]: Compiling to Wasm...
   Compiling asynctest v0.1.0
error[E0277]: the trait bound `JsValue: From<Point>` is not satisfied
  --> src/lib.rs:12:1
   |
12 | #[wasm_bindgen]
   | ^^^^^^^^^^^^^^^ the trait `From<Point>` is not implemented for `JsValue`
   |
   = help: the following other types implement trait `From<T>`:
             <JsValue as From<&'a T>>
             <JsValue as From<&'a std::string::String>>
             <JsValue as From<&'a str>>
             <JsValue as From<*const T>>
             <JsValue as From<*mut T>>
             <JsValue as From<JsError>>
             <JsValue as From<JsType>>
             <JsValue as From<bool>>
           and 81 others
   = note: required for `Point` to implement `Into<JsValue>`
   = note: required for `Point` to implement `IntoJsResult`
   = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `tests` (lib) due to previous error
Error: Compiling your crate to WebAssembly failed
Caused by: failed to execute `cargo build`: exited with exit status: 101
  full command: "cargo" "build" "--lib" "--release" "--target" "wasm32-unknown-unknown"

Both using rustc version 1.67.1 (d5a82bbd2 2023-02-07) and 1.71.0-nightly (8b4b20836 2023-05-22).
Saw a similar issue here but no real solution.

Edit: digging a bit deeper with cargo rustc --profile=check -- -Zunpretty=expanded revealed the following:

without async generates:

#[allow(dead_code)]
pub fn into_js() -> Point { Point { x: 0, y: 0 } }
#[automatically_derived]
const _: () =
    {
        pub unsafe extern "C" fn __wasm_bindgen_generated_into_js()
            -> <Point as wasm_bindgen::convert::ReturnWasmAbi>::Abi {
            let _ret = { let _ret = into_js(); _ret };
            <Point as wasm_bindgen::convert::ReturnWasmAbi>::return_abi(_ret)
        }
    };

whereas with async generates:

#[allow(dead_code)]
pub async fn into_js() -> Point { Point { x: 0, y: 0 } }
#[automatically_derived]
const _: () =
    {
        pub unsafe extern "C" fn __wasm_bindgen_generated_into_js()
            ->
                <wasm_bindgen::JsValue as
                wasm_bindgen::convert::ReturnWasmAbi>::Abi {
            let _ret =
                wasm_bindgen_futures::future_to_promise(async move
                            {
                            {
                                let _ret = into_js();
                                <Point as
                                        wasm_bindgen::__rt::IntoJsResult>::into_js_result(_ret.await)
                            }
                        }).into();
            <wasm_bindgen::JsValue as
                    wasm_bindgen::convert::ReturnWasmAbi>::return_abi(_ret)
        }
    };
@sgantrim
Copy link

sgantrim commented Aug 7, 2023

This solved the async problem for me:

impl From<Point> for JsValue { fn from(value: Point) -> Self { serde_wasm_bindgen::to_value(&value).unwrap() } }

@sgantrim
Copy link

sgantrim commented Aug 8, 2023

Messed around with this a little more to see if I could automate the trait implementation - otherwise, each Rust structure would have to individually provide the impl block. Turns out a derive macro works great.

use proc_macro::TokenStream;
use quote::quote;
use syn;

#[proc_macro_derive(TsifyAsync)]
pub fn tsify_async_macro_derive(input: TokenStream) -> TokenStream {
    // Construct a representation of Rust code as a syntax tree
    // that we can manipulate
    let ast = syn::parse(input).unwrap();

    // Build the trait implementation
    impl_tsify_async_macro(&ast)
}

fn impl_tsify_async_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    let gen = quote! {
        impl From<#name> for JsValue {
            fn from(value: #name) -> Self {
                serde_wasm_bindgen::to_value(&value).unwrap()
            }
        }
    };
    gen.into()
}

From there we just have to add the macro as a dependency, then add TsifyAsync to the derive section of the struct.

#[derive(Debug, Serialize, Deserialize, Tsify, TsifyAsync)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct Foo {
    pub text: String,
    pub yes_or_no: bool,
    pub magic_number: i32,
}

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

2 participants