Struct field type from trait associated type with a lifetime bound

Hello,

I'm using a struct-trait combo in order to map a generic type on a struct to another associated type used by the struct's fields. This seems to work great (with the slight annoyance that I really don't want the trait to have to be public, but I can live with that).

However, when I complicate things by giving the inner type a lifetime bound, I don't know how to express the constraint in the struct definition without giving the struct an additional explicit lifetime.

I believe the internal lifetime for the struct's generic type should be sufficient. I think the lifetime I want to refer to only needs to exist within the implementation of the struct/trait and I don't want an additional lifetime to leak into the struct's interface, if possible.

pub struct MyStruct<F = ()>
    where Self: Wrapper<F>
{
    _val: Option<<Self as Wrapper<F>>::InnerT>,
    func: F,
}

pub trait Wrapper<F>
{
    type InnerT<'a> where F: 'a;
}

impl Wrapper<()> for MyStruct<()> {
    type InnerT = ();
}

impl<F: Fn() -> f64> Wrapper<F> for MyStruct<F> {
    type InnerT<'a> = &'a F;
}

impl<F: Fn() -> f64> MyStruct<F> {
    pub fn with_fn(func: F) -> Self {
        Self{
            _val: None,
            func: func
        }
    }
}

pub fn main() {
    let _my_struct = MyStruct::with_fn(|| 3.1415);
}

Is there a way to use for<'a> or something to tie together the lifetime bounds of the generic F and a lifetime on a trait or a trait's associated type?

Thank you for any ideas.

I don't really understand what you are referring here, because "the struct's generic type" is pretty ambiguous. Do you mean the struct type itself, the associated type, or something else?

The associated type has a generic lifetime parameter (that's a parameter, not a bound!), so it has to come from somewhere. It can't just be left out.

Maybe my nomenclature wasn't clear. I mean the F in pub struct MyStruct<F = ()>

All I want to express is that F outlives InnerT .

The 'a lifetime parameter on InnerT is just a means to an end. If there is another way to get a lifetime bound by F then I'm all for it.

I'd be just as happy to move the lifetime parameter to the trait instead of the associated type. But that doesn't feel like it gets closer to a solution.

Ie, this is a legal way to introduce a lifetime that can be used as a parameter

where for<'a> Self: Wrapper<'a, F>

but this isn't

_val: Option<<for<'a> Self as Wrapper<'a, F>>::InnerT>,

You can't have a lifetime bound field and not add it to the structure.
Below is with you GAT code but could possibly instead add lifetime to Wrapper.

I get the impression your trying to store a value that's life is shorter than the structure, it can't be done as it is unsafe.

pub struct MyStruct<'s, F = ()>
where
    Self: Wrapper<F>,
    F: 's,
{
    _val: Option<<Self as Wrapper<F>>::InnerT<'s>>,
    func: F,
}

pub trait Wrapper<F> {
    type InnerT<'a>
    where
        F: 'a;
}

impl Wrapper<()> for MyStruct<'static, ()> {
    type InnerT<'a> = ();
}

impl<'s, F: Fn() -> f64> Wrapper<F> for MyStruct<'s, F> {
    type InnerT<'a> = &'a F where F: 'a;
}

impl<'a, F: Fn() -> f64> MyStruct<'a, F> {
    pub fn with_fn(func: F) -> Self {
        Self {
            _val: None,
            func: func,
        }
    }
}

pub fn main() {
    let _my_struct = MyStruct::with_fn(|| 3.1415);
}

Thank you for the reply. However, putting the lifetime as a parameter on the struct is what I'm trying to avoid. That is the reason I asked this question.

The way I was thinking about it is that F has an implied lifetime, and that there might be a syntax to access that lifetime in order to bound other types that are internal to the implementation, without putting in another explicit lifetime parameter in the struct interface.

In one place, you are using InnerT without a lifetime parameter, but in another place, you are using it with one. This doesn't make any sense.

I don't believe projecting out lifetimes is possible, as there are no "associated lifetimes" in the language. You should probably be working with a non-generic associated type instead, and talk about only the type as a whole, instead of trying to project out a lifetime just for the sake of feeding it back into the GAT.

However, that raises the question: what should be the lifetime in the following part of the code?

impl<F: Fn() -> f64> Wrapper<F> for MyStruct<F> {
    type InnerT = &'a F;
}

What are you trying to achieve by using a reference here? If you expand the generic into concrete types, then you get the following for the struct definition:

pub struct MyStruct<F> {
    _val: Option<&'a F>,
    func: F,
}

which…:

  1. still needs a lifetime parameter because you are storing a reference; it can't just be inferred from whatever lifetime parameters F has, because it is for a different purpose; and
  2. looks a lot like you are trying to be self-referential, which is not expressible in safe Rust.

I smell a design error here, which no amount of lifetime annotations will solve. What are you trying to do? What high-level goal do you have in mind? Why are you trying to store a reference to the function in the same struct?

2 Likes

What I understand is you wonder what lifetime you should give to innerT<'?> in MyStruct, without giving MyStruct a lifetime parameter.

pub struct MyStruct<F = ()>
    where Self: Wrapper<F>
{
    _val: Option<<Self as Wrapper<F>>::InnerT<'?>>,
    func: F,
}

The compromise way, suitable to your situation, just to give MyStruct an another generic type parameter without any trait bounds.

pub struct MyStruct<F = (), T = ()> {
    _val: T,
    func: F,
}

Then added a equality judgment to where you use both F and T.

impl<F, T> MyStruct<F, T> {
  fn return_inner<'a>() -> T
  where
    Self: Wrapper<F, InnerT<'a> = T>
  { todo!() }
}

But I would suggest you to use lifetime parameter, without knowing more about your final goal. If you worry about the initialization, just give it a 'static lifetime.

1 Like

This is really the essence of my question. I now see that I didn't do a good job redacting my real use case to the simplified code sample, and therefore there are a lot of red herrings in my sample code.

In any case "It's not possible" is still a valid answer, so I'll mark your answer as the solution.

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.