Stream: git-wasmtime

Topic: wasmtime / issue #9776 Missing examples for using bindgen...


view this post on Zulip Wasmtime GitHub notifications bot (Dec 10 2024 at 16:30):

ifsheldon opened issue #9776:

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

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(())
            })
        }
    }

}

view this post on Zulip Wasmtime GitHub notifications bot (Dec 10 2024 at 16:30):

ifsheldon edited issue #9776:

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

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(())
            })
        }
    }

}

view this post on Zulip Wasmtime GitHub notifications bot (Dec 10 2024 at 17:34):

fitzgen commented on issue #9776:

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.

view this post on Zulip Wasmtime GitHub notifications bot (Dec 11 2024 at 02:26):

ifsheldon commented on issue #9776:

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

view this post on Zulip Wasmtime GitHub notifications bot (Dec 12 2024 at 20:39):

alexcrichton commented on issue #9776:

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.

view this post on Zulip Wasmtime GitHub notifications bot (Dec 13 2024 at 03:19):

ifsheldon commented on issue #9776:

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.

view this post on Zulip Wasmtime GitHub notifications bot (Dec 13 2024 at 17:34):

alexcrichton commented on issue #9776:

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

view this post on Zulip Wasmtime GitHub notifications bot (Dec 20 2024 at 19:52):

alexcrichton closed issue #9776:

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

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(())
            })
        }
    }

}

Last updated: Dec 23 2024 at 12:05 UTC