7

I have a struct that needs a callback that returns a future whose output has a lifetime:


struct Foo;
struct Bar;

struct Baz<F>
where F: for<'a> FnOnce(&'a Foo) -> impl std::future::Future<Output=&'a Bar> // ?
{
    cb: F
}

That doesn't compile, with a syntax error since impl can't appear in trait bounds:

   Compiling playground v0.0.1 (/playground)
error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
 --> src/lib.rs:6:37
  |
6 | where F: for<'a> FnOnce(&'a Foo) -> impl std::future::Future<Output=&'a Bar>,
  |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

This doesn't compile either:


struct Foo;
struct Bar;

struct Baz<O, F>
where
    O: for<'a> std::future::Future<Output=&'a Bar>,
    F: for<'b> FnOnce(&'b Foo) -> O,
{
    cb: F
}

Complaining that 'a isn't recognized (and the lifetimes 'a and 'b would be unrelated):

   Compiling playground v0.0.1 (/playground)
error[E0582]: binding for associated type `Output` references lifetime `'a`, which does not appear in the trait input types
 --> src/lib.rs:7:36
  |
7 |     O: for<'a> std::future::Future<Output=&'a Bar>,
  |                                    ^^^^^^^^^^^^^^

error: aborting due to previous error

Is there a way to specify this relationship?

Colonel Thirty Two
  • 23,953
  • 8
  • 45
  • 85
  • Can you include the specific errors for "didn't compile"? Try not to paraphrase, you might miss something important. Just include the exact error text. – tadman Dec 16 '20 at 03:10
  • @tadman I can, but it's not going to help. Neither of the approaches are correct. – Colonel Thirty Two Dec 16 '20 at 03:14
  • The specific errors are a lot more informative, and also if someone else with this exact problem has the same error, now they can find this. – tadman Dec 16 '20 at 03:48

2 Answers2

5

It's a bit ugly, but this is the best way I could find (using struct Foo(Bar) for demonstration):

Playground

use std::future::Future;

struct Bar;
struct Foo(Bar);

trait FooClosure<'a> {
    type Fut: Future<Output = &'a Bar>;

    fn call(self, foo: &'a Foo) -> Self::Fut;
}

impl<'a, Fut: Future<Output = &'a Bar>, C: FnOnce(&'a Foo) -> Fut> FooClosure<'a> for C {
    type Fut = Fut;

    fn call(self, foo: &'a Foo) -> Fut {
        self(foo)
    }
}

struct Baz<F: for<'a> FooClosure<'a>>(F);

fn main() {
    let closure: Box<dyn for<'a> FnOnce(&'a Foo) -> futures::future::Ready<&'a Bar>> =
        Box::new(|foo| futures::future::ready(&foo.0));
    let baz = Baz(closure);
}

I couldn't get the compiler to infer the types properly, so I had to wrap the closure as boxed trait object. Theoretically this shouldn't be necessary, and there might be a way to avoid this but I couldn't figure it out.

Note: I think there are plans to make your original code work without needing this trait black magic.

Coder-256
  • 5,212
  • 2
  • 23
  • 51
  • 1
    One way to help the compiler with type inference without requiring the annoying boxing is to introduce yet another helper function: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3016d1b6ed02c6ec89ee3d4a8c2e058e – mcarton Jan 20 '21 at 23:23
-1

A straightforward way to do this is to parameterize the struct Baz itself with the lifetime parameter:

struct Baz<'a, O: Future<Output=&'a Bar>, F: FnOnce(&'a Foo) -> O> {
    cb: F,
    phantom: PhantomData<&'a O>,
}

The PhantomData instance is required to "use" the lifetime parameter 'a and the output type parameter O. You can think of O and 'a as components of the closure type F -- they will always be implicitly inferred from the lifetime and return type used by F.

Playground

EvilTak
  • 7,091
  • 27
  • 36
  • 1
    This isn't exactly the same thing OP is asking for, since it forces you to make one choice of `'a` work everywhere for the same instance of `Baz`, instead of having each `'a` be chosen at the call site of the closure. – trent Jan 26 '21 at 16:51