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?
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.
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>,
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:
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
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?
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.
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.