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

Missing examples for using bindgen! async, imports and resource in host #9776

Open
ifsheldon opened this issue Dec 10, 2024 · 5 comments · May be fixed by #9822
Open

Missing examples for using bindgen! async, imports and resource in host #9776

ifsheldon opened this issue Dec 10, 2024 · 5 comments · May be fixed by #9822

Comments

@ifsheldon
Copy link

ifsheldon commented Dec 10, 2024

Feature

There are no examples about using bindgen! with async: true, imports and resources in Rust hosts. Please add examples to do that.

A bit of background

I have a string.wit and a guest component.

package component:big-string;


interface large-string {
  resource largestring {
    constructor();
    push: func(s: string);
    get: func() -> string;
    clear: func();
  }
}

world big-string {
  import large-string;
  import print: func(s: string) -> bool;
  export manipulate-large-string: func() -> string;
}

I want to run this component in a Rust host. It took me quite a while to debug and inspect the generated code with cargo-expand to implement this. The code is much more complicated than the and there's no way for IDE to help.

I didn't have any clues how to write the code until I cargo-expanded the macro.

You should at least add an example in bindgen_example to give a first impression to newcomers about the complexity.

And probably you can simplify the traits of the async version with latest Rust async functions in traits.

Benefit

  • Adding an example in bindgen_example can give a first impression to newcomers about the complexity
  • Probably simplifying it in bindgen with latest Rust async functions in traits can ultimately remove these annoying lifetimes, Pin and Box in function signatures, which will enable IDE to autocomplete and help better.

Implementation

I can make a PR to submit my code to be an example.

Full Code

use crate::utils::get_component_linker_store;
use crate::utils::{bind_interfaces_needed_by_guest_rust_std, ComponentRunStates};
use wasmtime::component::bindgen;
use wasmtime::component::Resource;
use wasmtime::Engine;


mod sync_version {
    use super::*;

    bindgen!({
        path: "../wit-files/string.wit",
        world: "big-string",
        with: {
            "component:big-string/large-string/largestring": LargeString
        }
    });

    pub struct LargeString {
        pub storage: String,
    }

    impl BigStringImports for ComponentRunStates {
        fn print(&mut self, string: String) -> bool {
            println!("from print sync host func: {}", string);
            true
        }
    }

    impl component::big_string::large_string::Host for ComponentRunStates {}

    impl component::big_string::large_string::HostLargestring for ComponentRunStates {
        fn new(&mut self) -> Resource<LargeString> {
            self.resource_table
                .push(LargeString {
                    storage: String::new(),
                })
                .unwrap()
        }

        fn push(&mut self, resource: Resource<LargeString>, s: String) -> () {
            let large_string = self.resource_table.get_mut(&resource).unwrap();
            large_string.storage.push_str(s.as_str());
        }

        fn get(&mut self, resource: Resource<LargeString>) -> String {
            let large_string = self.resource_table.get(&resource).unwrap();
            format!("sync: {}", large_string.storage)
        }

        fn clear(&mut self, resource: Resource<LargeString>) -> () {
            let large_string = self.resource_table.get_mut(&resource).unwrap();
            large_string.storage.clear();
        }

        fn drop(&mut self, resource: Resource<LargeString>) -> wasmtime::Result<()> {
            let _ = self.resource_table.delete(resource)?;
            Ok(())
        }
    }
}

mod async_version {
    use futures::executor::block_on;

    use super::*;
    use core::future::Future;
    use core::pin::Pin;

    bindgen!({
        path: "../wit-files/string.wit",
        world: "big-string",
        async: true,
        with: {
            "component:big-string/large-string/largestring": LargeString
        },
    });

    pub struct LargeString {
        pub storage: String,
    }

    impl BigStringImports for ComponentRunStates {
        fn print<'life, 'async_trait>(
            &'life mut self,
            s: String,
        ) -> Pin<Box<dyn Future<Output = bool> + Send + 'async_trait>>
        where
            'life: 'async_trait,
            Self: 'async_trait,
        {
            // TODO: make a PR for this
            Box::pin(async move {
                println!("from print async host func: {}", s);
                true
            })
        }
    }

    impl component::big_string::large_string::Host for ComponentRunStates {}

    impl component::big_string::large_string::HostLargestring for ComponentRunStates {
        fn new<'life, 'async_trait>(
            &'life mut self,
        ) -> Pin<Box<dyn Future<Output = Resource<LargeString>> + Send + 'async_trait>>
        where
            'life: 'async_trait,
            Self: 'async_trait,
        {
            Box::pin(async {
                self.resource_table
                    .push(LargeString {
                        storage: String::new(),
                    })
                    .unwrap()
            })
        }

        fn push<'life, 'async_trait>(
            &'life mut self,
            resource: Resource<LargeString>,
            s: String,
        ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
        where
            'life: 'async_trait,
            Self: 'async_trait,
        {
            Box::pin(async move {
                let large_string = self.resource_table.get_mut(&resource).unwrap();
                large_string.storage.push_str(s.as_str());
            })
        }

        fn get<'life, 'async_trait>(
            &'life mut self,
            resource: Resource<LargeString>,
        ) -> Pin<Box<dyn Future<Output = String> + Send + 'async_trait>>
        where
            'life: 'async_trait,
            Self: 'async_trait,
        {
            Box::pin(async move {
                let large_string = self.resource_table.get(&resource).unwrap();
                format!("async: {}", large_string.storage)
            })
        }

        fn clear<'life, 'async_trait>(
            &'life mut self,
            resource: Resource<LargeString>,
        ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
        where
            'life: 'async_trait,
            Self: 'async_trait,
        {
            Box::pin(async move {
                let large_string = self.resource_table.get_mut(&resource).unwrap();
                large_string.storage.clear();
            })
        }

        fn drop<'life, 'async_trait>(
            &'life mut self,
            resource: Resource<LargeString>,
        ) -> Pin<Box<dyn Future<Output = wasmtime::Result<()>> + Send + 'async_trait>>
        where
            'life: 'async_trait,
            Self: 'async_trait,
        {
            Box::pin(async {
                let _ = self.resource_table.delete(resource)?;
                Ok(())
            })
        }
    }

}
@fitzgen
Copy link
Member

fitzgen commented Dec 10, 2024

In general, adding an example of async bindgen! would be great!

On the particulars of what the example is doing and whether your given example is best, I don't have super strong opinions. A timer might be smaller and more realistic? Not 100% clear to me.

Maybe @alexcrichton has opinions.

@ifsheldon
Copy link
Author

Thanks!

Regarding this long function signature, do you know any ways to simplify it? I thought the stabilized async functions in traits might help. I don't know if async-trait crate can help, either.

-> Pin<Box<dyn Future<Output = SomeReturnType> + Send + 'async_trait>>
        where
            'life: 'async_trait,
            Self: 'async_trait

@alexcrichton
Copy link
Member

Definitely feel free to add an example with a PR, it would be most welcome!

All of wasmtime's async support was designed before async functions were available in traits, and yes nowadays we should remove the need for #[async_trait] ideally. That'll require using -> impl Future<...> + Send in the trait definitions (can't just use raw async fn just yet I think), but the trait implementors can still use async fn ... in that case.

If you're up for it a PR to remove #[async_trait] would be most welcome, but I suspect that would be a pretty involved PR as well.

@ifsheldon
Copy link
Author

Definitely feel free to add an example with a PR, it would be most welcome!

I will make a PR with a modified example that makes a bit more sense.

All of wasmtime's async support was designed before async functions were available in traits, and yes nowadays we should remove the need for #[async_trait] ideally

So, bindgen indeed uses #[async_trait] internally? I couldn't see this when I just did cargo-expand which expands all macros recursively. If so, I think the users/implmentors can also leverage #[async_trait] to simplify the impl block.

That'll require using -> impl Future<...> + Send in the trait definitions (can't just use raw async fn just yet I think), but the trait implementors can still use async fn ... in that case.

We can use trait_variant mentioned in the Announcing async fn and return-position impl Trait in traits so we don't even need to write -> impl Future<...> + Send in bindgen's implementation ourselves. Just something like

#[trait_variant::make(HostLargestring: Send)]
pub trait LocalHostLargestring {
    async fn new(&mut self) -> Resource<LargeString>;
    // ...
}

If you're up for it a PR to remove #[async_trait] would be most welcome, but I suspect that would be a pretty involved PR as well.

Yeah, this may take a while. I will just make a PR to add an example first. Then I will open another tracking issue for this.

@alexcrichton
Copy link
Member

Using trait_variant makes sense to me yeah! And yes bindings use #[async_trait] which is why generated docs aren't great.

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

Successfully merging a pull request may close this issue.

3 participants