Storing async functions which have references (possibly HRTB related)

Hey all,

I have been trying to figure this out for the past week. What I am trying to do is store async functions and call them whenever I want however many times I want.

I have it working if the async fns I want to store takes the values directly.

use tokio; // 1.2.0
use futures; // 0.3.12
use futures::prelude::*;
use std::pin::Pin;

trait System: Send + Sync + 'static {
    fn call<'a, 't>(&'a self, n: u8) -> Pin<Box<dyn Future<Output = u8> + Send + 't>>
    where
        'a: 't;
}

impl<T, F> System for T
where
    T: Fn(u8) -> F + Send + Sync + 'static,
    F: Future<Output = u8> + Send + 'static,
{
    fn call<'a, 't>(&'a self, n: u8) -> Pin<Box<dyn Future<Output = u8> + Send + 't>>
    where
        'a: 't
    {
        Box::pin((self)(n))
    }
}

async fn a(n: u8) -> u8 {
    println!("executing a");
    n + 1
}


async fn b(mut n: u8) -> u8 {
    n = n + 1;
    n + 2
}

#[tokio::main]
async fn main() {
    let mut v: Vec<Box<dyn System>> = vec![];
    let mut n: u8 = 2;

    v.push(Box::new(a));
    v.push(Box::new(b));
    
    println!("pushed");

    for i in &v {
        n = i.call(n).await;
        println!("{:?}", n);
    }

    for i in &v {
        n = i.call(n).await;
        println!("{:?}", n);
    }
}

But when I want the async fns to take references, I am running into a few problems.

use tokio; // 1.2.0
use futures; // 0.3.12
use futures::prelude::*;
use std::pin::Pin;

trait System: Send + Sync + 'static {
    fn call<'a, 't>(&'a self, n: &mut u8) -> Pin<Box<dyn Future<Output = u8> + Send + 't>>
    where
        'a: 't;
}

impl<T, F> System for T
where
    T: Fn(&mut u8) -> F + Send + Sync + 'static,
    F: Future<Output = u8> + Send + 'static,
{
    fn call<'a, 't>(&'a self, n: &mut u8) -> Pin<Box<dyn Future<Output = u8> + Send + 't>>
    where
        'a: 't
    {
        Box::pin((self)(n))
    }
}

async fn a(n: &mut u8) -> u8 {
    println!("executing a");
    *n + 1
}


async fn b(n: &mut u8) -> u8 {
    *n = *n + 1;
    *n + 2
}

#[tokio::main]
async fn main() {
    let mut v: Vec<Box<dyn System>> = vec![];
    let mut n: u8 = 2;

    v.push(Box::new(a));
    v.push(Box::new(b));
    
    println!("pushed");

    for i in &v {
        println!("{:?}", i.call(&mut n).await);
    }

    for i in &v {
        println!("{:?}", i.call(&mut n).await);
    }
}

This gives me the following compile error:

error: implementation of `FnOnce` is not general enough
   --> src/main.rs:41:12
    |
41  |       v.push(Box::new(a));
    |              ^^^^^^^^^^^ implementation of `FnOnce` is not general enough
    |
    = note: `FnOnce<(&'0 mut u8,)>` would have to be implemented for the type `for<'_> fn(&mut u8) -> impl futures::Future {a}`, for some specific lifetime `'0`...
    = note: ...but `FnOnce<(&mut u8,)>` is actually implemented for the type `for<'_> fn(&mut u8) -> impl futures::Future {a}`

error: implementation of `FnOnce` is not general enough
   --> src/main.rs:42:12
    |
42  |       v.push(Box::new(b));
    |              ^^^^^^^^^^^ implementation of `FnOnce` is not general enough
    |
    = note: `FnOnce<(&'0 mut u8,)>` would have to be implemented for the type `for<'_> fn(&mut u8) -> impl futures::Future {b}`, for some specific lifetime `'0`...
    = note: ...but `FnOnce<(&mut u8,)>` is actually implemented for the type `for<'_> fn(&mut u8) -> impl futures::Future {b}`

Researching a bit, I get pointed to several questions on stackoverflow and to the following issues on github:

I tried to correct the above code by specifying a lifetime for the reference in the async function:

use tokio; // 1.2.0
use futures; // 0.3.12
use futures::prelude::*;
use std::pin::Pin;

trait System<'r>: Send + Sync {
    fn call<'a>(&'a self, n: &'r mut u8) -> Pin<Box<dyn Future<Output = u8> + Send + 'r>>
    where
        'a: 'r;
}

impl<'r, T, F> System<'r> for T
where
    T: Fn(&'r mut u8) -> F + Send + Sync,
    F: Future<Output = u8> + Send + 'r,
{
    fn call<'a>(&'a self, n: &'r mut u8) -> Pin<Box<dyn Future<Output = u8> + Send + 'r>>
    where
        'a: 'r,
    {
        Box::pin((self)(n))
    }
}

async fn a(n: &mut u8) -> u8 {
    println!("executing a");
    *n + 1
}


async fn b(n: &mut u8) -> u8 {
    *n = *n + 1;
    *n + 2
}

#[tokio::main]
async fn main() {
    let mut v: Vec<Box<dyn System>> = vec![];
    let mut n: u8 = 2;

    v.push(Box::new(a));
    v.push(Box::new(b));
    
    println!("pushed");

    for i in &v {
        println!("{:?}", i.call(&mut n).await);
    }

    for i in &v {
        println!("{:?}", i.call(&mut n).await);
    }
}

but it gives me the following compile error:

error[E0597]: `v` does not live long enough
  --> src/main.rs:46:14
   |
46 |     for i in &v {
   |              ^^
   |              |
   |              borrowed value does not live long enough
   |              argument requires that `v` is borrowed for `'static`
...
53 | }
   | - `v` dropped here while still borrowed

error[E0499]: cannot borrow `n` as mutable more than once at a time
  --> src/main.rs:47:33
   |
47 |         println!("{:?}", i.call(&mut n).await);
   |                          -------^^^^^^-
   |                          |      |
   |                          |      `n` was mutably borrowed here in the previous iteration of the loop
   |                          argument requires that `n` is borrowed for `'static`

error[E0499]: cannot borrow `n` as mutable more than once at a time
  --> src/main.rs:51:33
   |
51 |         println!("{:?}", i.call(&mut n).await);
   |                          -------^^^^^^-
   |                          |      |
   |                          |      `n` was mutably borrowed here in the previous iteration of the loop
   |                          argument requires that `n` is borrowed for `'static`

I would really appreciate if someone can help me understand what exactly I am missing and get this working or if it's not possible right now in Rust.

I don't think it's possible. Currently the call argument is tied to the dyn System type, so:

  • borrow of n must live at least as long as all of v. Traits can't refer to lifetimes shorter than their own. 'a: 'r doesn't help. You can't make a "dangling" lifetime, so you end up requiring these lifetimes to be the same.

  • n is an mut borrow. This means you can use the whole System at most once, because it gets tied to an exclusive invariant borrow.

You'd need the trait to use fn call<'r>(&self, n: &'r mut u8) to untangle it from the argument, and use T: for<'r> Fn(&'r mut u8) -> F + Send + Sync, but then I see no way to tie F to 'r.

@kornel Thanks for the answer. I guess that is what HRTB refers to?

You helped me understand how to correctly solve this. We just create another trait to represent the async fn.

use tokio; // 1.2.0
use futures; // 0.3.12
use futures::prelude::*;
use std::pin::Pin;

trait AsyncFn<'a>: Send + Sync + 'static {
    fn f(&'a self, n: &'a mut u8) -> Pin<Box<dyn Future<Output = u8> + Send + 'a>>;
}

impl<'a, T, F> AsyncFn<'a> for T
where
    T: Fn(&'a mut u8) -> F + Send + Sync + 'static,
    F: Future<Output = u8> + Send + 'a
{
    fn f(&'a self, n: &'a mut u8) -> Pin<Box<dyn Future<Output = u8> + Send + 'a>>
    {
        Box::pin((self)(n))
    }
}

trait System: Send + Sync + 'static {
    fn call<'a>(&'a self, n: &'a mut u8) -> Pin<Box<dyn Future<Output = u8> + Send + 'a>>;
}

impl<T> System for T
where
    T: for<'r> AsyncFn<'r>,
{
    fn call<'a>(&'a self, n: &'a mut u8) -> Pin<Box<dyn Future<Output = u8> + Send + 'a>>
    {
        self.f(n)
    }
}

async fn a(n: &mut u8) -> u8 {
    println!("executing a");
    *n + 1
}


async fn b(n: &mut u8) -> u8 {
    *n = *n + 1;
    *n + 2
}

#[tokio::main]
async fn main() {
    let mut v: Vec<Box<dyn System>> = vec![];
    let mut n: u8 = 2;

    v.push(Box::new(a));
    v.push(Box::new(b));
    
    println!("pushed");

    for i in &v {
        println!("{:?}", i.call(&mut n).await);
    }

    for i in &v {
        println!("{:?}", i.call(&mut n).await);
    }
}
2 Likes

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.