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

Unable to use async functions on instance methods #1858

Closed
kurbaniec opened this issue Nov 9, 2019 · 3 comments
Closed

Unable to use async functions on instance methods #1858

kurbaniec opened this issue Nov 9, 2019 · 3 comments
Labels

Comments

@kurbaniec
Copy link

Using the async keyword on normal functions works fine, but if I want to use it on a method for an object I get some compiler errors that I assume are lifetime related:

#[wasm_bindgen]
pub struct Worker {
    user: PouchDB,
    group: PouchDB,
}

#[wasm_bindgen]
impl Worker {
    // ...
    pub async fn process(&self, command: String) -> Result<JsValue, JsValue> {
        let output = match command.as_str() {
            "adapter" => { self.user.adapter() }
            "info" => {
                match JsFuture::from(self.user.info()).await {
                    Ok(resolved) => {
                        match resolved.into_serde::<Info>() {
                            Ok(val) => format!("{:?}", &val),
                            Err(_) => "Deserialize error".to_string(),
                        }
                    },
                    Err(_) => "Promise error".to_string(),
                }
            }
            _ => String::from("Unknown command")
        };
        Ok(JsValue::from(output))
    }
}

Compiler error:

error[E0597]: `me` does not live long enough
  --> src\lib.rs:34:1
   |
34 | #[wasm_bindgen]
   | ^^^^^^^^^^^^^^-
   | |             |
   | |             `me` dropped here while still borrowed
   | borrowed value does not live long enough
   | argument requires that `me` is borrowed for `'static`

I tried to implement the fix that the compiler suggests:

pub async fn process(&'static self, command: String) -> Result<JsValue, JsValue>

But then I get the error that is already mentioned in the issue #1187:

error: it is currently not sound to use lifetimes in function signatures
  --> src\lib.rs:47:27
   |
47 |     pub async fn process(&'static self, command: String) -> Result<JsValue, JsValue> {
   |    

As for now the only workaround working for me is creating a normal async function, that consumes the object and returns it back at the end. The processed data is saved internally by the object.

#[wasm_bindgen]
pub struct Worker {
    user: PouchDB,
    group: PouchDB,
    output: String
}

pub async fn process(mut worker: Worker, command: String) -> Worker {
    let output = match command.as_str() {
        "adapter" => { worker.user.adapter() }
        "info" => {
            match JsFuture::from(worker.user.info()).await {
                Ok(resolved) => {
                    match resolved.into_serde::<Info>() {
                        Ok(val) => format!("{:?}", &val),
                        Err(_) => "Deserialize error".to_string(),
                    }
                },
                Err(_) => "Promise error".to_string(),
            }
        }
        _ => String::from("Unknown command")
    };
    worker.output = output;
    worker
}

Usage in JavaScript:

let worker = new wasm.Worker();
...
worker = await wasm.process(worker, data.msg);
let msg = worker.get_output();

Full workaround-code can be found here: https://github.com/U3W/EasyPass/blob/client-backend/WebService/src/main/rust/src/lib.rs

@kurbaniec kurbaniec added the bug label Nov 9, 2019
@Pauan
Copy link
Contributor

Pauan commented Nov 9, 2019

This isn't a problem with wasm-bindgen, it's just how Futures work in general, as you can see here:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7a198bf0c5b75f61f9da4576098fa13a

Because Futures run asynchronously, they always outlive the stack, so they must have the 'static lifetime. But &self is not static, it's stack allocated. You can see here for a more detailed technical explanation.

There are generally two workarounds:

  1. You can return impl Future and hoist all uses of self outside of the Future, like this:

    impl Worker {
        pub fn process(&self, command: String) -> impl Future<Output = Result<JsValue, JsValue>> {
            let info = JsFuture::from(self.user.info());
    
            async move {
                let output = match command.as_str() {
                    "adapter" => { self.user.adapter() }
                    "info" => {
                        match info.await {
                            Ok(resolved) => {
                                match resolved.into_serde::<Info>() {
                                    Ok(val) => format!("{:?}", &val),
                                    Err(_) => "Deserialize error".to_string(),
                                }
                            },
                            Err(_) => "Promise error".to_string(),
                        }
                    }
                    _ => String::from("Unknown command")
                };
                Ok(JsValue::from(output))
            }
        }
    }

    Notice how self.user.info() has been hoisted outside of the async Future.

  2. You can accept self by value rather than by reference (this is the workaround that you're using):

    impl Worker {
        pub async fn process(self, command: String) -> Result<JsValue, JsValue> {
            // ...
        }
    }

    But obviously then you can't use self after calling process. You can fix that by accepting Rc instead:

    impl Worker {
        pub async fn process(self: Rc<Self>, command: String) -> Result<JsValue, JsValue> {
            // ...
        }
    }

    And if you need to mutate self you can combine Rc with RefCell:

    impl Worker {
        pub async fn process(this: Rc<RefCell<Self>>, command: String) -> Result<JsValue, JsValue> {
            // ...
        }
    }

    Notice how we had to change it to use this rather than self. That's because Rust doesn't currently allow for using RefCell with self. That also means you would need to call it like Worker::process(worker, foo) rather than worker.process(foo)

@kurbaniec
Copy link
Author

Thank you very much @Pauan for your detailed answer! I finally understand the problem.

I tried applying your workarounds and ran to some problems.

The first workaround gives me following error:

error[E0667]: `impl Trait` is not allowed in path parameters
  --> src\lib.rs:97:19
   |
97 | pub fn testo() -> impl Future<Output = Result<JsValue, JsValue>> {
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
  --> src\lib.rs:97:19
   |
97 | pub fn testo() -> impl Future<Output = Result<JsValue, JsValue>> {
   |   

The second:

error[E0277]: the trait bound `std::rc::Rc<Worker>: wasm_bindgen::convert::traits::FromWasmAbi` is not satisfied
  --> src\lib.rs:37:1
   |
37 | #[wasm_bindgen]
   | ^^^^^^^^^^^^^^^ the trait `wasm_bindgen::convert::traits::FromWasmAbi` is not implemented for `std::rc::Rc<Worker>`

Fortunately, I found a solution after I checked again the wasm-bindgen-futures documentation. Instead of returning the rust future directly, it is converted to a JS promise first.

    pub fn process(&self, command: String) -> Promise {
        let info = JsFuture::from(self.user.info());
        let adapter = self.user.adapter();

        future_to_promise(async move {
            let output = match command.as_str() {
                "adapter" => adapter,
                "info" => {
                    match info.await {
                        Ok(resolved) => {
                            match resolved.into_serde::<Info>() {
                                Ok(val) => format!("{:?}", &val),
                                Err(_) => "Deserialize error".to_string(),
                            }
                        },
                        Err(_) => "Promise error".to_string(),
                    }
                }
                _ => String::from("Unknown command")
            };
            Ok(JsValue::from(output))
        })
    }

Sadly, its a bit more unconvient to write than an async function, but it works as intended.

@NickeZ
Copy link

NickeZ commented Dec 18, 2019

I would appreciate if the async book has a chapter on this. I ran into the same issue.

semenov-vladyslav added a commit to iotaledger/streams that referenced this issue Dec 7, 2020
wasm: added wasm-client cargo feature and enabled corresponding wasm
features in cargo deps

binding/wasm: updated bindings, notably Author and Subscriber now use
Rc<Ref<Api>> objects internally as async methods can only be 'self' and
not '&mut self', see
rustwasm/wasm-bindgen#1858 for discussion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants