I just published a little lib in Crates, and found the doc a bit not as expected.
The problem is on async traits. I totally understand that the doc is what the code actually is. But it is not quite friendly to read.
The code is like:
/// DOC
#[async_trait]
pub trait SOMETHING {
async fn something(&self) -> ();
}
And the code piece in doc is like:
pub trait SOMETHING {
fn something<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = ()> + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
}
How could I get this to the simpler async fn
version?
1 Like
alice
November 26, 2024, 6:04pm
2
Do you need the #[async_trait]
macro? If not, you can just remove it. Traits support async fn
natively nowadays.
1 Like
Good point. For now, I kind of need the async_trait
. I use the trait as object (dyn SOMETHING
) in another place, which does not support generic.
alice
November 26, 2024, 6:21pm
4
If you define the trait like this:
pub trait MyTrait {
fn foo(&self) -> impl Future<Output = String> + Send;
}
then it's possible to support trait objects with a workaround like this:
// first define a helper trait
#[async_trait]
trait MyTraitDyn: MyTrait {
async fn foo(&self) -> String;
}
#[async_trait]
impl<T: ?Sized + Send + Sync + MyTrait> MyTraitDyn for T {
async fn foo(&self) -> String {
MyTrait::foo(self).await
}
}
// then define a helper type
pub struct DynMyTrait {
inner: Box<dyn MyTraitDyn>,
}
impl DynMyTrait {
pub fn new<T: MyTrait + Send + Sync + 'static>(value: T) -> Self {
Self { inner: Box::new(value) }
}
}
impl MyTrait for DynMyTrait {
async fn foo(&self) -> String {
self.inner.foo().await
}
}
With the above, the definition of MyTrait
is at least improved somewhat. Implementers of MyTrait
can still use async fn
syntax despite the impl Future
definition. The impl Future
definition is unavoidable if you want to support dyn.
1 Like
Also check out the dynosaur
crate, which aims at doing pretty much this problem with dynamic dispatch.
Thanks for the info. Tried a bit, I think something is still missing.
For example, in my case, the generated structure is not public, I cannot use it in other modules. Also I have to impl a few derive-able traits.
Sorry for the late response, I tried a few times to fit your design into my code. I got some "cannot be made into an object" or lifetime issues....
So I cannot say if this would be a solution for now. Thanks any way.
Lej77
December 1, 2024, 9:14am
8
It is possible to slightly change this code in order to not need a special DynMyTrait
struct:
(Playground )
// Vtable for `MyTrait` will contain methods from `MyTraitDyn`.
#[expect(private_bounds)]
pub trait MyTrait: MyTraitDyn {
fn foo(&self) -> impl Future<Output = String> + Send
// Must use `Self: Sized` to remove
// problematic methods from vtable.
where
Self: Sized;
}
// first define a helper trait
#[async_trait]
trait MyTraitDyn {
async fn dyn_foo(&self) -> String;
}
#[async_trait]
impl<T: Sized + Send + Sync + MyTrait> MyTraitDyn for T {
async fn dyn_foo(&self) -> String {
<T as MyTrait>::foo(self).await
}
}
// Use the helper trait to define implementations
// for dynamically sized types:
/// `Box<dyn MyTrait + Send + Sync>`
impl<T: ?Sized + Send + Sync + MyTrait> MyTrait for Box<T> {
// Note: called with a `&Box<dyn MyTrait + Send + Sync>`
fn foo(&self) -> impl Future<Output = String> + Send {
<T as MyTraitDyn>::dyn_foo(self)
}
}
/// `&mut (dyn MyTrait + Send + Sync)`
impl<T: ?Sized + Send + Sync + MyTrait> MyTrait for &mut T {
// Note: called with a `&&mut (dyn MyTrait + Send + Sync)`
fn foo(&self) -> impl Future<Output = String> + Send {
<T as MyTraitDyn>::dyn_foo(self)
}
}
/// `&(dyn MyTrait + Send + Sync)`
impl<T: ?Sized + Send + Sync + MyTrait> MyTrait for &T {
// Note: called with a `&&(dyn MyTrait + Send + Sync)`
fn foo(&self) -> impl Future<Output = String> + Send {
<T as MyTraitDyn>::dyn_foo(self)
}
}
Implementors don't need to consider the extra details:
struct Foo;
impl MyTrait for Foo {
async fn foo(&self) -> String {
todo!()
}
}
Though calling the methods on a dynamically sized type sometimes gets a bit messy:
fn is_string_future(_: impl Future<Output = String>) {}
fn take_ref(value: &(dyn MyTrait + Send + Sync)) {
// This is unfortunately a compile error:
// is_string_future(value.foo());
// So we must add an extra borrow when calling foo:
is_string_future((&value).foo());
// Or call the dyn_foo method:
is_string_future(value.dyn_foo());
// Or specify the trait impl we want to use:
let future = <&(dyn MyTrait + Send + Sync) as MyTrait>::foo(&value);
is_string_future(future);
}
// Automatically coerced to trait object:
take_ref(&Foo);
Edit: I realized that the above code could make a user unnecessarily box their futures so here is a new version that should prevent that (used in the same way):
(Updated playground )
// Vtable for `MyTrait` will contain methods from `MyTraitDyn`.
#[expect(private_bounds)]
pub trait MyTrait: MyTraitDyn + Send + Sync {
fn foo(&self) -> impl Future<Output = String> + Send
// Must use `Self: Sized` to remove
// problematic methods from vtable.
where
Self: Sized;
}
// first define a helper trait
#[async_trait]
trait MyTraitDyn {
async fn dyn_foo(&self) -> String;
}
#[async_trait]
impl<T: Sized + Send + Sync + MyTrait> MyTraitDyn for T {
async fn dyn_foo(&self) -> String {
<T as MyTrait>::foo(self).await
}
}
// Use the helper trait to define implementations
// for dynamically sized types:
// `Box<Type> where Type: MyTrait`
impl<T: Sized + Send + Sync + MyTrait> MyTrait for Box<T> {
fn foo(&self) -> impl Future<Output = String> + Send {
<T as MyTrait>::foo(self)
}
}
impl MyTrait for Box<dyn MyTrait + '_> {
fn foo(&self) -> impl Future<Output = String> + Send {
<dyn MyTrait as MyTraitDyn>::dyn_foo(self)
}
}
// `&mut Type where Type: MyTrait`
impl<T: Sized + Send + Sync + MyTrait> MyTrait for &mut T {
fn foo(&self) -> impl Future<Output = String> + Send {
<T as MyTrait>::foo(self)
}
}
impl MyTrait for &mut (dyn MyTrait + '_) {
fn foo(&self) -> impl Future<Output = String> + Send {
<dyn MyTrait as MyTraitDyn>::dyn_foo(self)
}
}
// `&Type where Type: MyTrait`
impl<T: Sized + Send + Sync + MyTrait> MyTrait for &T {
fn foo(&self) -> impl Future<Output = String> + Send {
<T as MyTrait>::foo(self)
}
}
impl MyTrait for &(dyn MyTrait + '_) {
fn foo(&self) -> impl Future<Output = String> + Send {
<dyn MyTrait as MyTraitDyn>::dyn_foo(self)
}
}
If the trait has consuming methods (takes self
by value) then we can't implement the trait for references, see this playground .
1 Like