Return value `R: Future` borrows function parameter; how to express that correctly in a trait bound?

Hi, and sorry for the messy title ; I couldn't find a better way to introduce my problem.

I'm trying to implement a Defer3 trait, which is supposed to add a listen method to any function with the following signature:

struct Instance;
struct Ret;

for<'a> fn(&'a mut Instance, P1, P2, P3) -> impl Future<Output = Ret> + 'a;

The idea is that the listen method spawns an asynchronous task which handles deferred calls to the function, triggered by channel messages.

Here is the trait:

use async_lock::RwLock;
use async_channel::Sender;

trait Defer3<T, P1, P2, P3> {
    fn listen(&self, instance: RwLock<T>) -> Sender<(P1, P2, P3)>;
}

and here's how I'm trying to implement it for that kind of function:

use core::future::Future;

impl<'a, T: 'static, F, P1, P2, P3, R> Defer3<T, P1, P2, P3> for F

    where
        F: Fn(&'a mut T, P1, P2, P3) -> R,
        R: Future<Output = Ret> + 'a

{
    fn listen(&self, instance: RwLock<T>) -> Sender<(P1, P2, P3)> {
        let (tx, rx) = async_channel::unbounded();

        let _task = async move {
            while let Ok((p1, p2, p3)) = rx.recv().await {
                let mut locked = instance.write().await;
                let future = self(&mut *locked, p1, p2, p3);
                let _ret = future.await;
            }
        };

        tx
    }
}

Now, I'm lost regarding the compiler's messages:

error[E0207]: the type parameter `R` is not constrained by the impl trait, self type, or predicates
  --> crates/utils/src/lib.rs:54:41
   |
54 |     impl<'a, T: 'static, F, P1, P2, P3, R> Defer3<T, P1, P2, P3> for F
   |                                         ^ unconstrained type parameter

error[E0597]: `instance` does not live long enough
  --> crates/utils/src/lib.rs:66:38
   |
54 |     impl<'a, T: 'static, F, P1, P2, P3, R> Defer3<T, P1, P2, P3> for F
   |          -- lifetime `'a` defined here
...
61 |         fn listen(&self, instance: RwLock<T>) -> Sender<(P1, P2, P3)> {
   |                          -------- binding `instance` declared here
...
66 |                     let mut locked = instance.write().await;
   |                                      ^^^^^^^^ borrowed value does not live long enough
67 |                     let future = self(&mut *locked, p1, p2, p3);
   |                                  ------------------------------ argument requires that `instance` is borrowed for `'a`
...
70 |             };
   |             - `instance` dropped here while still borrowed

error[E0597]: `locked` does not live long enough
  --> crates/utils/src/lib.rs:67:45
   |
54 |     impl<'a, T: 'static, F, P1, P2, P3, R> Defer3<T, P1, P2, P3> for F
   |          -- lifetime `'a` defined here
...
66 |                     let mut locked = instance.write().await;
   |                         ---------- binding `locked` declared here
67 |                     let future = self(&mut *locked, p1, p2, p3);
   |                                  -----------^^^^^^-------------
   |                                  |          |
   |                                  |          borrowed value does not live long enough
   |                                  argument requires that `locked` is borrowed for `'a`
68 |                     let _ret = future.await;
69 |                 }
   |                 - `locked` dropped here while still borrowed
  1. why does the compiler tell me that R is unconstrained? It should be the return type of the Self function type.

  2. Once the future is used (after let _ret = future.await), locked shouldn't be borrowed anymore. I don't understand why locked is still borrowed when it must be dropped.

Here is a sample function for reference:

fn demo_fn<'a>(_this: &'a mut Instance, _p1: u8, _p2: u8, _p3: u8) -> impl Future<Output = Ret> + 'a {
    async {
        // in a more realistic scenario, _this is used here,
        // hence the lifetime binding on the return type.
        Ret
    }
}

Thank you!

That's because not all generic parameters of the Fn trait in the F: Fn(...) are bounded, so the compiler thinks that F could implement both Fn(&'a mut T, P1, P2, P3) -> R1 and Fn(&'b mut T, P1, P2, P3) -> R2 for two different lifetimes 'a and 'b and different types R1 and R2.

You probably don't want an unbounded 'a in the impl generics though, as the user of your trait can choose it (potentially choosing 'static). This is what causes the two borrow errors, as that requirement leads to locked and instance to be borrowed for an unconstrained lifetime, potentially after your function ends. You need is a higher-ranked trait bound (the for<'a> you used at the start of your post), but then the future type cannot name the lifetime 'a. This has been discussed recently in this question Is it possible to achieve these lifetimes in this generic FnOnce -> Future? - #3 by 2e71828 and TLDR: you have to either replace the future type with something like Pin<Box<dyn Future<Output = Ret>>> or use an AsyncFn from the async-fn-traits crate (which however will very likely lead you to lifetime inference errors that are not currently solvable).

3 Likes

Ah... I don't understand this possible compiler hesitation... Do you think I can avoid it somehow?

Can they really choose it? I thought the lifetime was directly that of locked, i.e. in no way 'static... I thought this lifetime was opaque for the caller of listen :thinking:

Indeed, this is a simple solution; however I was looking for a non-allocating solution. I think I'd rather implement this whole mechanism using a dirty macro (my initial attempt) than allocate for every deferred call :grin:

Overall thank you for your answer :+1:

Oh wow, The async_fn_traits crate solution worked like a charm!

    impl<T, F, P1, P2, P3> Defer3<T, P1, P2, P3> for F

        where
            for<'a> F: AsyncFn4<&'a mut T, P1, P2, P3, Output = Ret>

    {
        fn listen(&self, instance: RwLock<T>) -> Sender<(P1, P2, P3)> {
            let (tx, rx) = async_channel::unbounded();

            let _task = async move {
                while let Ok((p1, p2, p3)) = rx.recv().await {
                    let mut locked = instance.write().await;
                    let future = self(&mut *locked, p1, p2, p3);
                    let _ret = future.await;
                }
            };

            tx
        }
    }

This compiles and the demo_fn I posted above implements it :partying_face:

Note: the compiler still wants me to include the 'a parts for some reason.
Well, thank you very much!

Fn is just a generic trait. There's no reason the same type couldn't implement two separate instantiations/specializations of the "same" (generic) trait (because those are two traits).

Now if this is true, then you've left with an impl that has no functional dependence on one of the type parameters:

impl<T, U, R> MyTrait<T> for U {
    ...
}

This would implement the same trait for the same type multiple times (once for each choice of R). That's not allowed.

The for<'a> part is what makes it avoid this.

Well, kinda, it depends on how you "fix" the code to make it compile. When you have an unbound parameter in an impl you can either:

  • bound it somewhere: either the trait or the type's generic parameters, in this case only the trait was a possibility. This would lead to that problem I mentioned, and would be the wrong "fix";
  • remove it from the impl generics, in this case by moving it to the for<'a>
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.