ifsheldon opened issue #9776:
Feature
There are no examples about using
bindgen!withasync: true, imports and resources in Rust hosts. Please add examples to do that.A bit of background:
I have a
string.witand 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-expandto 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,
PinandBoxin 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(()) }) } } }
ifsheldon edited issue #9776:
Feature
There are no examples about using
bindgen!withasync: true, imports and resources in Rust hosts. Please add examples to do that.A bit of background
I have a
string.witand 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-expandto 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,
PinandBoxin 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 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.
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-traitcrate can help, either.-> Pin<Box<dyn Future<Output = SomeReturnType> + Send + 'async_trait>> where 'life: 'async_trait, Self: 'async_trait
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<...> + Sendin the trait definitions (can't just use rawasync fnjust yet I think), but the trait implementors can still useasync 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 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 didcargo-expandwhich expands all macros recursively. If so, I think the users/implmentors can also leverage#[async_trait]to simplify theimplblock.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_variantmentioned in the Announcingasync fnand return-positionimpl Traitin traits so we don't even need to write-> impl Future<...> + Sendin 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 commented on issue #9776:
Using
trait_variantmakes sense to me yeah! And yes bindings use#[async_trait]which is why generated docs aren't great.
alexcrichton closed issue #9776:
Feature
There are no examples about using
bindgen!withasync: true, imports and resources in Rust hosts. Please add examples to do that.A bit of background
I have a
string.witand 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-expandto 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,
PinandBoxin 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(()) }) } } }
Last updated: Dec 13 2025 at 19:03 UTC