Reputation: 386
I'm new to rust's async programming and encountered something weird.
I want to introduce async in traits without boxing overhead.
I'm using nightly with the #![feature(type_alias_impl_trait)]
feature.
Here's minimized code:
#![feature(type_alias_impl_trait)]
use std::path::Path;
use std::future::Future;
type Error = Box<dyn std::error::Error>;
#[tokio::main]
async fn main() -> Result<(), Error> {
tokio::spawn(async move {
"test".to_owned().super_async("x").await
});
Ok(())
}
trait AsyncTrait {
type Fut<'a, P>: Future<Output=()>
where
Self: 'a,
P: AsRef<Path> + 'a;
fn test_async<P: AsRef<Path>>(&self, p: P) -> Self::Fut<'_, P>;
}
impl AsyncTrait for String {
type Fut<'a, P> = impl Future<Output = ()> + 'a
where
Self: 'a,
P: AsRef<Path> + 'a;
fn test_async<P: AsRef<Path>>(&self, p: P) -> Self::Fut<'_, P> {
async move {
let bs = p.as_ref();
()
}
}
}
trait SuperAsync: AsyncTrait {
type SuperFut<'a, P>: Future<Output=()>
where
P: AsRef<Path> + 'a,
Self: 'a;
fn super_async<P: AsRef<Path>>(&self, p: P) -> Self::SuperFut<'_, P>;
}
impl<T: AsyncTrait> SuperAsync for T {
type SuperFut<'a, P> = impl Future<Output = ()> + 'a
where
P: AsRef<Path> + 'a,
Self: 'a;
fn super_async<P: AsRef<Path>>(&self, p: P) -> Self::SuperFut<'_, P> {
async move {
self.test_async(p).await;
()
}
}
}
Then I got the error message:
error: implementation of `AsRef` is not general enough
--> src/main.rs:45:5
|
45 | / tokio::spawn(async move {
46 | | "test".to_owned().super_async("x").await
47 | | });
| |______^ implementation of `AsRef` is not general enough
|
= note: `AsRef<Path>` would have to be implemented for the type `&'0 str`, for any lifetime `'0`...
= note: ...but `AsRef<Path>` is actually implemented for the type `&'1 str`, for some specific lifetime `'1`
I don't understand the error message. If I remove tokio::spawn
or impl SuperAsync
only for String
, then the error disappers. Any help?
Upvotes: 0
Views: 442
Reputation: 421
I was able to implement this:
#![feature(type_alias_impl_trait)]
use std::path::Path;
use std::future::Future;
type Error = Box<dyn std::error::Error>;
#[tokio::main]
async fn main() -> Result<(), Error> {
tokio::spawn(async move {
"test".to_owned().super_async("x").await
});
Ok(())
}
trait AsyncTrait {
type Fut<'a, P>: Future<Output=()>
where
Self: 'a,
P: AsRef<Path> + 'a + ?Sized;
fn test_async<'a, P: AsRef<Path> + ?Sized>(&self, p: &'a P) -> Self::Fut<'a, P>;
}
impl AsyncTrait for String {
type Fut<'a, P> = impl Future<Output = ()> + 'a
where
Self: 'a,
P: AsRef<Path> + 'a + ?Sized;
fn test_async<'a, P: AsRef<Path> + ?Sized>(&self, p: &'a P) -> Self::Fut<'a, P> {
async move {
let bs = p.as_ref();
()
}
}
}
trait SuperAsync: AsyncTrait {
type SuperFut<'a, P>: Future<Output=()>
where
P: AsRef<Path> + 'a + ?Sized,
Self: 'a;
fn super_async<'a, P: AsRef<Path> + ?Sized>(&'a self, p: &'a P) -> Self::SuperFut<'a, P>;
}
impl<T: AsyncTrait> SuperAsync for T {
type SuperFut<'a, P> = impl Future<Output = ()> + 'a
where
P: AsRef<Path> + 'a + ?Sized,
Self: 'a;
fn super_async<'a, P: AsRef<Path> + ?Sized>(&'a self, p: &'a P) -> Self::SuperFut<'a, P> {
async move {
self.test_async(p).await;
()
}
}
}
I changed the definition a little bit, now lifetime 'a
is associated to self
and P
, also changing P
into a reference, so we can also associate his and the AsRef
lifetimes to to 'a
.
By changing P
into a reference, we enforce that we are not consuming it, and the data that this reference points to will live longer then the future returned. Maybe there is a way to receive a owned P
value and ensure it's lifetime, but I couldn't figure that out.
The ?Sized
was added because P
solves into the str
type (not &str
) that is unsized.
Upvotes: 1