Lifetimes and Function pointers

Hi,

I'm struggling with the the following code (comments and motivation included):

I have some deserialization functions, which convert some input into some type.
Those deserialization functions are stored in a registry/collection.
In order to store the deserialization functions in a collection, I need to overcome lifetime-bounds.
Hence I added a for<'i> bound (which makes sense)
If I assume that the trait object is static, the code compiles & works!

In order to "register a type", I need to add the function to my collection.
In the 'static case - which compiles - the API is quite nice:
fn register<A:ObviousConstraints>(&mut self)
Unfortunately, as soon as I add a lifetimes (i.e, the trait object cannot outlive the input), I'm failing :frowning:

Thank you for your help!

First the fix to the playground:

    let f4: FnType = |input| work_generic::<S>(input);

which is just an inline version of work_s really.


Then the explanation: Function items with generic lifetimes that have explicit bounds are not higher ranked over those lifetimes, they're parameterized by those lifetimes.

Okay, that probably isn't illuminating at all. What I mean is that this function:

fn work_generic<'i, 'b, A>(_input: &'b mut Input<'i>) -> Box<dyn Trait + 'i>
where
    A: Default,
    A: Trait,
    A: 'i, // explicit bound involving 'i

has a function item type that is like

// No 'b as a paramater of the type because 'b has no explicit bounds
struct WorkGeneric<'i, A> { ... }

And each WorkGeneric::<'i, S>[1] can be coerced to a for<'b> fn(&'b mut Input<'i>) ..., but not a for<'b, 'i> fn .... Because WorkGeneric::<'*, S> or whatever is not a type; WorkGeneric::<'i, S> can only work with 'i specifically, not "any 'i" (for<'i>).

Whereas work_s doesn't have any parameters with explicit bounds at all, so the function item type doesn't have any generic parameters. And a non-capturing closure can work similarly.


Since you already discovered that work_s works, I'm not sure if this actually solves your core issue or not.


  1. corresponding to work_generic::<'i, S> ↩︎

1 Like

This works:

let f4: FnType = |input| work_generic::<S>(input);

Thank you and Alice for the super quick reply :smiley:

As expected, this doesn't solve my larger problem :frowning:

Here is the next step:

Now the compiler wants my type to be static
Note that in the previous snippet the type S was indeed static.
To double-check this, I adjusted the example so that S is not static, and this still compiles using your trick:

Now I'm even more confused. I tried multiple different things, and in the example with a type with lifetimes, everything works out, but the generic register fails.

type FnType = for<'i, 'b> fn(&'b mut Input<'i>) -> Box<dyn Trait + 'i>;

This type has an implied bound of 'i: 'b, but there is no upper limit on what 'i could be. You could always call this function pointer with a &mut Input<'static> and it has to eturn a Box<dyn Trait + 'static> when you do so (or not return, e.g. panic). That's the API surface ot the type.

You can't coerce an A: 'non_static to a dyn Trait + 'static -- that would be unsound.

So this must not be the API surface you want. You need something where 'i has an upper limit. Working in such limits are usually pretty ugly... but here's one way.

type FnType<'upper> = for<'i, 'b>
    fn(&'b mut Input<'i>, [&'i &'upper (); 0]) -> Box<dyn Trait + 'i>;
    // implies 'upper: 'i  ^^^^^^^^^^^^^^

struct Collection<'upper>(Vec<FnType<'upper>>);
impl<'upper> Collection<'upper> {
    fn register<A>(&mut self)
    where
        A: Trait + Default + 'upper,
    {
        self.0.push(|input, _guard| work_generic::<A>(input))
    }
}

You'll have to pass a dummy [] arg with this exact approach.

1 Like

It just inferred S<'static> here BTW. (Example.)

Thank you for the example.

I believe the following example shows that the lifetime does not need to be static:

At least, line 21 seems to prove this.

work_generic isn't making use of A. It's body uses a different type S<'i>. The only lifetime A can accept is 'static.

let f4: FnType = |input| work_generic::<S<'static>>(input);
1 Like

Ok, I believe I understand my problem now:

This is where I started:

type FnTypeStatic = fn(&mut Input) -> Box<dyn Trait>;

Then I had a registry/collection (simplified, actually a dictionary)

static REGISTRY : Vec<FnTypeStatic> = {...};

And this was all working fine.

But, then I came over an interesting type with a lifetime, implementing Trait.
So I adjusted:

type FnTypeLifetime<'i> = fn(&mut Input<'i>) -> Box<dyn Trait + 'i >;
static REGISTRY:Vec<FnTypeLifetime<'i> = {...};

But this didn't work out, because of the unbound lifetime 'i in the static.
So I added the for clause:

type FnType = for<'i> fn(&mut Input<'i>) -> Box<dyn Trait + 'i >;
static REGISTRY:Vec<FnTypeLifetime> = {...};

Morally, this should work - it makes sense.
And it actually does: I just rewrote my whole machine, with lots of dyn Trait +'i, and it just works fine.
So, thank you all for your help!


Why didn't I notice this before?

When I was write the code for the static use case, I wanted to be generic over traits.
Unfortunately, this is not possible (right?).
So I wrote my code to be generic over dyn Trait instead.
The actual starting point was

type FnTypeStatic<T> = fn(&mut Input) -> Box<T>;

and then I added my lifetime like this

type FnTypeStatic<'i, T:'i> = fn(&mut Input<'i>) -> Box<T>;

and then I lost the right track when trying to figure out how to do this :frowning:
First specializing to a fixed trait and then implementing the lifetime turned out to be much easier than the other way around.

New Question: Is there some syntax for the (restricted, dependent) product

type FnTypeStatic<T> = for<'i> where T:'i fn(&mut Input<'i>) -> Box<T>;

(The lifetime-in-a-box syntax works only for traits ...)

Just to comment on your actual answer, which put me on the right track.
This is the type which I started with:

type FnType = for<'i> fn(&mut Input<'i>) -> Box<dyn Trait + 'i>;

Then there is a collection/dictionary of those, and given some input,

input:Input<'i>

one takes the right entry from the dictionary and can apply this entry to the input.
So it is actually the API surface I wanted
The issue was that I named all lifetimes, in a hope to get a better handle on the problem, and then you had the impression, that this lifetimes 'b was actually important to me (which it wasn't and I was aware of that it wasn't, but I was not sure enough that I was actually correct ...)
So thank you again, for the effort and the impressive quick responses!

I'm not sure I understand what you mean. If you mean something like

type FnTypeStatic<T> = fn(&mut Input) -> Box<T>;

where T can be dyn Trait + '_,[1] that requires removing the implicit Sized bound on T.

type FnTypeStatic<T: ?Sized> = fn(&mut Input) -> Box<T>;

Side note: Sounds like you don't need it any more, but here's a version of upper lifetime limits that doesn't require a dummy arg (but it does make 'upper invariant). And I noticed you no longer need a closure wrapper if you move the bound to 'upper.

The for<'i where T: 'i> concept? If so, there's no direct way to write upper-bounded for<..> lifetime binders so far.

Where T can contain 'i somehow? Not directly; that would mean T is a type constructor,[2] and Rust doesn't have generic type constructors.[3] Generic type parameters like T always resolve to a single type (and types that vary by lifetime are different types).

And it doesn't look like "is a fn pointer" penetrates aliases, so you can't split it up with type aliases.

You could use a trait (and type alias) instead:

// Edit: oops failed to paste everything
trait FnTy {
    type Output<'i>: ?Sized + 'i;
}

// `M` is some representative type that implements `FnTy`
type FnTypeStatic<M> = for<'i>
    fn(&mut Input<'i>) -> Box<<M as FnTy>::Output<'i>>;

// Here I made the implementor and the output related, but they don't
// have to be (you could use empty enum marker types or whatever)
impl FnTy for dyn Trait {
    type Output<'i> = dyn Trait + 'i;
}

But then you need an implementation per type constructor.

And this may also wreck inference depending on how it's used.


  1. or dyn for<'a> Trait<..> + '_ ↩︎

  2. or higher-kinded type ↩︎

  3. or higher-kinded types ↩︎

In no_std environments like embedded devices, there isn't heap allocation so it's impossible to use Boxed closures, can Rust provide generic closures (but not generic functions) for no_std environments?

I'm not sure I understand the question. If it's just "Box<dyn _> on embedded how?", there's some box-esque-but-fixed-size-on-the-stack libraries intended for embedded, but I don't know much about them. You'd probably be better off creating a new embedded thread about that topic specifically.