Lifetime introduction hell please help :)

Hi everyone, I am bad at rust so please bear with me, but trying to get comfortable with it,

I am writing a small TODO application in Rust with TUI, using the ratatui crate,
I have defined a marker trait that requires the implementation of StatefulWidget

use ratatui::widgets::{StatefulWidget, Widget};
use crate::widgets::TodoList;

pub struct ViewState<'a> {
    todo_list: &'a TodoList,
}

pub trait View<'a>: StatefulWidget<State = ViewState<'a>> {}

note StatefulWidget doesn't accept any lifetime arguments


I want to implement the View trait on the ListView type, but I can't since
the struct doesn't take any lifetime arguments

use super::{View, ViewState};
use ratatui::prelude::*;

pub struct ListView;

impl<'a> StatefulWidget for ListView {
    type State = ViewState<'a>;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {}
}

impl<'a> View<'a> for ListView {}

error message:

error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates
 --> src/views/list.rs:6:6
  |
6 | impl<'a> StatefulWidget for ListView {
  |      ^^ unconstrained lifetime parameter

you can see the State type is only used in the function as argument type

 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) 

if I understand correctly this function signature actually equivalent to

 fn render(self, area: Rect, buf: &mut Buffer, state: &mut ViewState<'a>) 

but to make the function signature actually correct, we should introduce the lifetime only
for that function right? like so

 fn render<'a>(self, area: Rect, buf: &mut Buffer, state: &mut ViewState<'a>) 

I have tried everything (GPT didn't help)

P.S: didn't try it yet, but I think I can replace the todo_list pointer with Rc right? although I prefer not to but if this is the only solution I might have no option

The only probable lifetime-using approach is to implement the trait for something that has a lifetime itself, like &'a ListView or such.[1]

impl<'a> StatefulWidget for &'a ListView {
    type State = ViewState<'a>;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {}
}

impl<'a> View<'a> for &'a ListView {}

But probably the API is expecting the state to be something self-contained, not something parameterized by a lifetime. Using Rc instead may work; using something like an index instead of references may also work. (Hard to say with the amount of details we have.)


  1. "Probable" because this may also introduce borrowing problems elsewhere ↩ī¸Ž

Can you please make some self contained example? I can't quite figure out where do you expect TodoList to live and how ListView is related to it

1 Like

Unless you have a plan for a very specific memory management pattern that is designed around temporarily scoped data (such as React-like UI with a memory pool), use of references in structs will only create endless compilation-breaking impossible to satisfy situations.

Most UIs have to use Rc or Arc due to use of hierarchies of widgets combined with event handlers or cross-widget references that do not fit the strictly scoped borrow checking model, especially when there's no interior mutability that would make the temporary references slightly less restrictive (mutable ones are strictly exclusive).

Remember that Rust's temporary references are only one of multiple reference types Rust has. They are not better — they have specific semantic of forbidding storing the data and holding on to it beyond a certain scope of a loan. Adding them to structs doesn't make them better, it makes them temporary view types tied to the location they're borrowing from. If you're not managing that consciously, it usually ends up borrowing from random variables scattered all around the code, and the lifetimes become impossible to satisfy dangling pointers to destroyed data that used to be on stack somewhere.

tldr: don't put references in structs. It doesn't do what novice Rust users assume, and it's often a design error.

7 Likes

This is possible to solve (i.e. compile) using a PhantomData field on ListView.

struct ListView<'a> {
    _phantom: PhantomData<&'a ()>,
}

Whether that's reasonable in your app though is another question. In Ratatui, the StatefulWidget trait is only really necessary if you need to pass data back from the render call. You might be able to avoid that by implementing Widget for &ViewState or WidgetRef for ViewState (requires a feature flag as we haven't settled on a final design for this yet for various reasons....).

If you want to go deeper on the Ratatui side of this, I'd encourage you to post more details about it on the Ratatui forum. I suspect there's probably a bit of an XY problem at hand here that could benefit from seeing the full picture.

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.