I've got a (for me) slightly unusual lifetimes problem. Usually my lifetime problems involve heroic battles with the compiler, but now I find myself in a spot where I have two versions of my code and both work without any errors, yet because I don't really know what I'm doing, I don't know which one is "more correct".
Long story short(ish): I'm making a tui-rs based app, which after some tweaking has evolved into a more or less MVC-like architecture: there 's AppState (the model), a command manager which creates commands that mutate the AppState, and a UI component that takes immutable references to AppState to draw the things on the screen.
Now, say I have some "UIComponent". It contains a subcomponent which requires a lifetime parameter (because it contains an &str, for example). It also contains references to DataRows, which comes from the AppState.
My first (working) implementation of this looked roughly like this:
pub struct UIComponent<'a> {
subcomponent: Option<Subcomponent<'a>>
data: Vec<&'a DataRows>
}
impl<'a> UIComponent<'a> {
pub fn new(rows: &[&'a DataRows]) -> UIComponent<'a> {
UIComponent {
subcomponent: None,
data: rows.to_owned()
}
}
...
}
This works. As I understand it, what I'm saying here is that all references to things need to live at least as long as UIComponent. This is true for the embedded subcomponent (because it is managed wholly by UIComponent), and for AppState, since the app state is created in main, and is the "most global" struct in the app.
However, while refactoring some things today, I noticed that this might not be entirely correct, but I'm not sure. Mainly because the generic lifetime syntax still confuses me. I slap them everywhere until the compiler stops complaining and then things just work (thanks, Rust).
My thought was that, semantically at least, this somehow seems to link the lifetimes of the app state and the ui component, which seems incorrect. AppState lives way longer than any one ui component.
So, expecting to learn things by having the compiler blow up in my face, I experimented with adding more lifetime parameters:
pub struct UIComponent<'a, 'b>
where 'b: 'a
{
subcomponent: Option<Subcomponent<'a>>
data: Vec<&'b DataRows>
}
impl<'a, 'b> UIComponent<'a, 'b> {
pub fn new(rows: &[&'b DataRows]) -> UIComponent<'a, 'b> {
UIComponent {
subcomponent: None,
data: rows.to_owned()
}
}
...
}
Unfortunately, however, the compiler happily agreed with me and didn't give a peep. Everything just works like it did before. So now I'm wondering which one is "more correct" (or more idiomatic, if you will). I feel like I've gained more information in the actual struct definition in how the lifetimes relate to each other, but (for example) the definition of the "new" fn has become more muddled. Since subcomponent is simply initialized as None, the only lifetime information is the &[&'b DataRows] parameter, yet I still have to return a UIComponent with <'a, 'b> lifetime parameters, even if the 'a is semantically seemingly meaningless here.
Furthermore, while in the first example I could sort of reason about the lifetime of the actual UIComponent (it is equal (?) to the lifetime of the subcomponent and shorter (?) than the references to app state data), in the second example I seemingly lose some of this information, since the UIComponent is now related to two lifetimes, but obviously any UIComponent is only going to actually have one lifetime.
So essentially, my question is: which one of these implementations would be "more correct", and more importantly: why?