How to genericize a type on a Reference with any lifetime?

#1

How can I make a struct that is generic on a type which may be a reference, but does not have a lifetime parameter? The snippet below shows what I’m trying to do. There are two structs that both wrap Fn. Foo is 'static because it doesn’t have any non-'static parameters. Bar, when instantiated with &u32, u32, will have exactly the same contents as Foo. However, it won’t be 'static because of the &u32 parameter. It clearly could be 'static; only the generic parameter stops it. There must be a way to do this, because Fn does it. Is there a way to create a custom struct with higher-rank trait bounds?

use std::any::Any;
use std::marker::PhantomData;

struct Foo<F: Fn(&u32) -> u32 + 'static> {
    f: Box<F>
}

impl<F> Foo<F>
where F: Fn(&u32) -> u32 + 'static
{
    fn new(f: F) -> Self {
        Foo{f: Box::new(f)}
    }
}

struct Bar<I, O> {
    f: Box<dyn Fn(I) -> O + 'static>,
    _i: PhantomData<I>,
    _o: PhantomData<O>,
}

impl<I, O> Bar<I, O>
{
    fn new(f: Box<Fn(I) -> O>) -> Self {
        Bar{f, _i: PhantomData, _o: PhantomData}
    }
}

// That this compiles proves that Foo.f has no lifetime issues
fn foo_is_u64<F: Fn(&u32) -> u32 + 'static>(f: &Foo<F>) -> bool {
    Any::is::<u64>(f)
}

// This fails to compile even though Bar.f is static.
// It fails just because of the I and O in Bar's type signature.
fn bar_is_u64<I, O>(b: &Bar<I, O>) -> bool {
    Any::is::<u64>(b)
}

fn main() {
    // Proves that There are 'static FnMut(&u32) -> u32 objects
    let foo = Foo::new(|x| *x);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0310]: the parameter type `O` may not live long enough
  --> src/main.rs:37:20
   |
36 | fn bar_is_u64<I, O>(b: &Bar<I, O>) -> bool {
   |                  - help: consider adding an explicit lifetime bound `O: 'static`...
37 |     Any::is::<u64>(b)
   |                    ^
   |
note: ...so that the type `Bar<I, O>` will meet its required lifetime bounds
  --> src/main.rs:37:20
   |
37 |     Any::is::<u64>(b)
   |                    ^

error[E0310]: the parameter type `I` may not live long enough
  --> src/main.rs:37:20
   |
36 | fn bar_is_u64<I, O>(b: &Bar<I, O>) -> bool {
   |               - help: consider adding an explicit lifetime bound `I: 'static`...
37 |     Any::is::<u64>(b)
   |                    ^
   |
note: ...so that the type `Bar<I, O>` will meet its required lifetime bounds
  --> src/main.rs:37:20
   |
37 |     Any::is::<u64>(b)
   |                    ^

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0310`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.

#2

Actually that is desugared to this:

fn foo_is_u64<'b, F: for<'a> Fn(&'a u32) -> u32 + 'static>(f: &'b Foo<F>) -> bool

And this leads us to be able to desugar the second one: (Not sure if this is actually what it is desugared to, this is just a way to look at it; Fn* traits are weird)

fn bar_is_u64<'i, 'o, 'a, I: 'i, O: 'o>(b: &'a Bar<I, O>) -> bool

In other words, it’s possible that O is, say, this:

pub struct O<'a, T: Sized> {
    data: &'a T
}

Which wouldn’t be okay, say that the Fn is this:

|x: O| {
    O.data
}

Then there’d be no guarantee that the data returned is the same lifetime, because 'i != 'o.

And after a few minutes of trying to fix your example, I’ve come to the realization that this isn’t possible: Dynamic dispatch doesn’t guarantee very many things, and therefore can’t be used with lifetimes like this, although chances are that I’m probably wrong and that this is possible through the black magic of HRTBs and lifetimes.


Or just make everything 'static:

use std::any::Any;
use std::marker::PhantomData;

struct Bar<I: 'static, O: 'static>{
    f: Box<dyn Fn(I) -> O + 'static>,
    _i: PhantomData<I>,
    _o: PhantomData<O>,
}

impl<I: 'static, O: 'static> Bar<I, O>
{
    fn new(f: Box<Fn(I) -> O>) -> Self {
        Bar{f, _i: PhantomData, _o: PhantomData}
    }
}
fn bar_is_u64<I: 'static, O: 'static>(b: &Bar<I, O>) -> bool {
    Any::is::<u64>(b)
}

So to recap and actually try to tackle your question instead of analyzing it:
What you want is something like this:

struct Bar<I, O> 
where for<'a> I: 'a, O: 'a
#3

That’s not a problem at all, because it also applies to Fn(&u32) -> &u32. I can’t hope for my wrapped struct to be more generic than Fn. I’m just trying to make it no more restrictive than Fn.

Yeah, that’s more like it. Except that it doesn’t compile. It seems the for<'a> doesn’t apply to the O? Do you know how to fix it?

17 | where for<'a> I: 'a, O: 'a
   |                         ^^ undeclared lifetime

If that HRTB worked, and if the resulting struct were 'static, then I would be set.

#4

The HRTB is attached to each clause, not to the where itself.

where for<'a> I: 'a, for<'a> O: 'a
#5

But in that syntax, the lifetimes of I and O aren’t related, correct? The two 'a s are completely different, right? In any case, that still doesn’t fix the harder problem, which is that the resulting struct is not 'static.

#6

for<'a> T: 'a is effectively T: 'static anyway, since one possible 'a is 'static.

#7

But then how would we express this:

where F: Fn(I) -> O 
where F: 'static
where for<'a> I and O: 'a

so that anything can be put into and taken out of F but the object itself is the same.
For example how would the following function be modeled?

fn do_something<'a>(data: &'a str) -> &'a str {
    data[1..4]
}

Wouldn’t it be

F: for<'a> Fn(&'a str) -> &'a str + 'static

but then how would we generalize it even more to a lifetime bounds standpoint?

F: for<'a> Fn(T: 'a) -> U: 'a + 'static
#8

The compiler disagrees with you. I modifed my original example with this HRTB, but the compiler still insists that Bar isn’t 'static because I and O aren’t.
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=c875c3ca52935c76d3bfeec4d3b2338c

#9

I didn’t mean that they are literally considered equivalent, but you can infer that T would have to be 'static to satisfy the HRTB version.