Let's assume there is a trait Foo
:
trait Foo {
type Future: Future<Output = ()>;
fn bar(&self, a: &str) -> Self::Future;
}
Which has a required method bar
that takes a reference to self as well as a reference to a string slice and returns a Future.
Let's implement Foo
for any fn
that takes a reference to a string slice and returns the appropriate future.
impl<Fut: Future<Output = ()>> Foo for fn(&str) -> Fut {
type Future = Fut;
fn bar(&self, a: &str) -> Self::Future {
self(a)
}
}
Now, let's try to call bar
on an async fn
that takes a reference to a string slice.
async fn test(a: &str) {
/* ... */
}
#[tokio::main]
async fn main() {
Foo::bar(&(test as fn(&str) -> _), "Hello world");
}
Although test
has the correct function signature, and it returns the right future, the type cast from test_function
to fn(&str) -> _
fails.
error[E0605]: non-primitive cast: `for<'a> fn(&'a str) -> impl Future<Output = ()> {test}` as `for<'a> fn(&'a str) -> _`
--> src/main.rs:21:15
|
21 | Foo::bar(&(test as fn(&str) -> _), "Hello world");
| ^^^^^^^^^^^^^^^^^^^^^^^ invalid cast
Try it yourself
From my understanding, this is reason do to the binding of the impl Futere
, that is returned from test
, to the parameter lifetime a
.
The only, non-nightly solution, I could think of was to introduce a lifetime in the trait.
trait Foo<'a> {
type Future: Future<Output = ()> + 'a;
fn bar(&self, a: &'a str) -> Self::Future;
}
impl<'a, Fut: Future<Output = ()> + 'a> Foo<'a> for fn(&'a str) -> Fut {
type Future = Fut;
fn bar(&self, a: &str) -> Self::Future {
self(a)
}
}
With some very experimental features in nightly, I came up with this:
#![feature(return_position_impl_trait_in_trait)]
use std::future::Future;
trait Foo {
fn bar<'a>(&self, a: &'a str) -> impl Future<Output = ()> + 'a;
}
impl<Fut> Foo for fn(&str) -> Fut
where
for<'a> Fut: Future<Output = ()> + 'a,
{
fn bar(&self, a: &str) -> Fut {
self(a)
}
}
async fn test(a: &str) {
/* ... */
}
#[tokio::main]
async fn main() {
Foo::bar(&(test as fn(&str) -> _), "Hello world");
}
Try it yourself
But this still results in the same non-primitive type cast error.
Whenever I run into weird lifetime issues like this my first instinct is always to explicitly write out the lifetimes and see where we mess up.
Let's go and write out the lifetimes we expect:
trait Foo<'s> {
type Future: Future<Output = ()>;
fn bar<'t>(&'t self, a: &'s str) -> Self::Future;
}
impl<'s, Fut: Future<Output = ()>> Foo<'s> for fn(&'s str) -> Fut {
type Future = Fut;
fn bar<'t>(&'t self, a: &'s str) -> Self::Future {
self(a)
}
}
Now our code *just works* if we change as fn(&str) -> _
to as fn(_) -> _
:
async fn test(a: &str) {
/* ... */
}
#[tokio::main]
async fn main() {
Foo::bar(&(test as fn(_) -> _), "Hello world");
}
Wait, what? This is really weird, right? How come we just explicitly wrote out the lifetimes and now our program just works? Why do we need the as fn(_) -> _
? Why can't we use as fn(&str) -> _
? To be honest I have no clue. Regardless of cause, hopefully this answer helps you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With