Issue with FnOnce and async function with a &reference type argument

This is how far I've managed to minimize my original issue and I don't really understand what is the issue with the lifetime.

use std::future::Future;

struct Worker<FnLoad,LoadFut> 
where 
    LoadFut: Future<Output = ()>,
    FnLoad: Fn(&usize) -> LoadFut,
{
    load: FnLoad,
}


async fn build(_: &usize) {}


fn main() 
{
    let w = Worker {
        load: build,
    };
}

(Playground)

error:

Compiling playground v0.0.1 (/playground)
error: implementation of `FnOnce` is not general enough
   --> src/main.rs:17:13
    |
3   | / struct Worker<FnLoad,LoadFut> 
4   | | where 
5   | |     LoadFut: Future<Output = ()>,
6   | |     FnLoad: Fn(&usize) -> LoadFut,
7   | | {
8   | |     load: FnLoad,
9   | | }
    | |_- due to a where-clause on `Worker`...
...
17  |       let w = Worker {
    |               ^^^^^^ doesn't satisfy where-clause
    |
    = note: ...`FnOnce<(&'0 usize,)>` would have to be implemented for the type `for<'_> fn(&usize) -> impl Future {build}`, for some specific lifetime `'0`...
    = note: ...but `FnOnce<(&usize,)>` is actually implemented for the type `for<'_> fn(&usize) -> impl Future {build}`

It seems as the issue is that compiler cannot "connect" the lifetime of FnLoad and LoadFut and it is not possible to for<'a> each generic argument with the "same" 'a lifetime, but it seems as I've managed to solve it through a helper trait:
Playground

for more info check: stackoverflow

The first thing to know is that async fn build (_: &'_ usize) {} is a function with the following signature:

fn build<'any> (_: &'any usize) -> impl 'any + Future<Output = ()>

So, as you can see, this involves a "generic lifetime" within the callable API / interface / contract / trait, which is called a Higher-Rank Trait Bound: this matches an infinite set of trait bounds, and, for each lifetime parameter, there is a specific return type, different from the others.

So your -> LoadFut bounds would never have been able to work, since it is not infected with / is generic over that lifetime parameter.

A signature that would should have worked (it doesn't because of a bug with the trait solver when it involves higher-order lifetimes), is the following:

#![feature(unboxed_closures)]

use ::core::future::Future;

struct Worker<FnLoad>
where
    // "FnLoad : for<'any> Fn(&'any usize) -> impl 'any + Future<Output = ()>"
    for<'any>
        FnLoad
        :
        Fn<(&'any usize,)>
    ,
    for<'any>
        <FnLoad as FnOnce<(&'any usize,)>>::Output
        :
        Future<Output = ()>
    ,
{
    load: FnLoad,
}

It can be made to work when we are able to name the opaque return type of that async fn:

#![feature(type_alias_impl_trait)]
// #![feature(unboxed_closures)]

use ::core::future::Future;

struct Worker<FnLoad>
where
    for<'any>
        FnLoad
        :
        Fn(&'any usize) -> load_Ret<'any>
    ,
    for<'any>
        load_Ret<'any>
        :
        Future<Output = ()>
    ,
    /* The fact that the two clauses above pass and the *
     * following one doesn't, does prove there is a bug */
    // for<'any>
    //     <FnLoad as FnOnce<(&'any usize,)>>::Output /* i.e. load_Ret<'any> */
    //     :
    //     Future<Output = ()>
    // ,
{
    load: FnLoad,
}

#[allow(nonstandard_style)]
type load_Ret<'__> = impl '__ + Future<Output = ()>;

fn load (_: &'_ usize) -> load_Ret<'_>
{async move {
    
}}

fn main () 
{
    let _ = Worker { load }.load;
}

It is quite interesting to see that a helper trait does let Rust resolve what the unboxed_closures-specified-return-type-bound failed to do, is quite interesting, and a nice thing to keep up one's sleeve.

  • Minor nit: your Wrapper trait ought to take &self and not self (or the bound ought to be loosened to FnOnce)

    trait receiver
    FnOnce self
    FnMut &mut self
    Fn &self
2 Likes

I work around it by naming the lifetime to 'static.

use std::future::Future;

struct Worker<F, R>
where
    R: Future<Output = ()>,
    F: Fn(&'static usize) -> R,
{
    _f: F,
}

async fn build(_: &usize) {}

fn main() {
    let _ = Worker { _f: build };
    let _ = Worker { _f: |_| async {} };
}

(Playground)