Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trait binding lifetimes of Futures to fn arguments

Tags:

rust

traits

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.

like image 628
Michael Avatar asked Sep 16 '25 21:09

Michael


1 Answers

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.

like image 54
virchau13 Avatar answered Sep 19 '25 19:09

virchau13