How does generic lifetime parameter mix with generic type parameter?

Given a simple function with lifetime parameter:

struct Foo<'a> {...}

fn make_foo<'a>(x: &'a u32) -> Foo<'a> { ... }

How convert that function to generic that works for any structs with life time parameter?

My naive try did not work:

fn make<'a, T>(x: &'a u32) -> T<'a> { ... }

Where rust compiler warn me at T<'a> that "lifetime argument not allowed" which is kind of expected.

Thanks for any help or insight how this could be done!

Type parameters always represent a single type [1], and there is no general way to do that. You can approximate it with a trait and higher-ranked bounds:

trait MakeSomething<'a> {
    type Something: 'a;
    fn make(x: &'a u32) -> Self::Something;
}

trait MakeAnyOfThem: for<'any> MakeSomething<'any> {}
impl<T: for<'any> MakeSomething<'any>> MakeAnyOfThem for T {}

Or when GATs stabilize:

trait MakeAnyOfThem {
    type Something<'a>;
    fn make(x: &u32) -> Self::Something<'_>;
}

  1. in contrast with something that takes a lifetime parameter to construct a type ↩︎

1 Like

Thanks! That GAT thing looked interesting. Just make sure I understand correctly, do you mean I can construct Foo with

struct FooMaker {}
impl MakeAnyOfThem for FooMaker {
  type Something<'a> = Foo<'a>
  fn make(x: &'a u32) -> Foo<'a> { ... }
}

Is that right?

Also, does this definition of MakeAnyOfThem works for associated type with no lifetime parameter, or one lifetime parameter+more type parameters?

Here is an example that compiles:

#![feature(generic_associated_types)]

trait MakeAnyOfThem {
    type Something<'a>;
    fn make_func<'a>(x: &'a u32) -> Self::Something<'a>;
    fn make_method<'a, 'b>(&'a self, x: &'b u32) -> Self::Something<'b>;
}

struct Foo<'a> {
    _inner: &'a u32,
}

struct FooMaker {}

impl MakeAnyOfThem for FooMaker {
    type Something<'a> = Foo<'a>;
    fn make_func<'a>(x: &'a u32) -> Foo<'a> {
        Foo { _inner: x }
    }
    fn make_method<'a, 'b>(&'a self, x: &'b u32) -> Foo<'b> {
        Foo { _inner: x }
    }
}

fn main() {
    let i = 5;
    let _foo1 = FooMaker::make_func(&i);
    let maker = FooMaker {};
    let _foo2 = maker.make_method(&i);
}

(Playground)

Thanks everyone!

After some try and error, I found a solution much closer to original question:

#![feature(generic_associated_types)]

// defining generic `make()`
trait MakeAnyOfThem {
    type Something<'a> where Self: 'a;
    fn get<'a> (value: &'a u32) -> Self::Something<'a>;
}

fn make<'a, T: MakeAnyOfThem>(e : &'a u32) -> T::Something<'a>{
    T::get(e)
}

// defining `Foo` that supports generic `make()`
struct Foo<'a> {
    value: &'a u32
}

impl MakeAnyOfThem for Foo<'static> {
    type Something<'a> = Foo<'a>; // also works with non-generic type

    fn get<'b> (value: &'b u32) -> Foo<'b> {
        return Foo{value}
    }
}


fn main() {
    let value = 42;
    let foo = make::<Foo>(&value);

    println!("{}", foo.value);
}

Your solution with a 'static lifetime reminds me of my idea here (note that I didn't apply that idea to mmtkvdb yet, so the links to mmtkvdb below do not incorporate that idea):

As said in that post, I wasn't sure whether this idea is feasible because of the complex syntax.

I was also worried about issues regarding variance, i.e. the compiler doesn't know that for a generic type T: MakeAnyOfThem the type T::Something<'a> is a supertype of T::Something<'static>. That is, if I understand it right, the compiler doesn't know that values of T::Something<'static> can always be stored in variables of T::Something<'a> unless the compiler knows what T::Something<'lt> actually is in a particular scenario for a particular T. See also: Discussion on IRLO regarding GATs and variance.

So far I have been refraining from using this "'static trick" because I didn't really overlook all the consequences yet, but maybe it works well in your case. (And maybe it would work well in my case too; I might consider using it as well.)


  1. Something that I have previously (half jokingly) described as being of kind ' -> *. ↩︎

I'm not fully understand how 'static works there but I think it is less important in this case, as it is only for method resolution, never get involved into any computation. The worst thing I can imagine is that explicitly type that 'static every time when calling like make<Foo<'static>>(...).

But thanks for the heads up, it was interesting to learn this topic :slight_smile:

When you write make::<Foo>(&value) without a lifetime for Foo, then the compiler will try to infer that lifetime (i.e. use a lifetime that fits). The only fitting lifetime here would be 'static, so that's what the compiler chooses (I think).

So basically the compiler turns it automatically into make::<Foo<'static>>(&value) for you.

Using Foo<'static> as a type constructor seems a bit like a "hack" (or dirty trick) to me (even though I had the same idea for my use case). Maybe the cleaner way would be something like the following:

#![feature(generic_associated_types)]

trait MakeAnyOfThem {
    type Something<'a> where Self: 'a;
    fn get<'a> (value: &'a u32) -> Self::Something<'a>;
}

fn make<'a, T: MakeAnyOfThem>(e : &'a u32) -> T::Something<'a>{
    T::get(e)
}

enum FooMaker {}

struct Foo<'a> {
    value: &'a u32
}

impl MakeAnyOfThem for FooMaker {
    type Something<'a> = Foo<'a>;

    fn get<'b> (value: &'b u32) -> Foo<'b> {
        return Foo{value}
    }
}

fn main() {
    let value = 42;
    let foo = make::<FooMaker>(&value);
    println!("{}", foo.value);
}

(Playground)

Here FooMaker is a data type that has no value (not even a single one like ()), and such a data type is called zero variant enum. It solely exists to be passed as a type argument at compile-time.

Sometimes it is better to use unit-like structs (i.e. "struct FooMaker;") instead, but I think in the above case a zero variant enum is a good choice.

Edit: Maybe a unit-like struct is better. See also this thread if you're interested in that.

Edit #2: Also, I'm unsure if any of this actually makes sense for your use case. Perhaps there exists an easier way for whatever you want/need to achieve.

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.