Lifetime mismatch

Hello,
this is my first time posting here with a problem I am not able to solve myself and I really hope you can help me.
I have minimized the code as good as I could. My basic idea behind this is that I have a GUIHandler struct that keeps a vec with all GUI trait objects. When I create the first MainGUI everything works. But when I want to add a GUI in the InputHandlers test() function the lifetime mismatch error pops up. I want to create the GUI in the test() function, because I would like to open a GUI when I press a key.
I am out of ideas how to solve this.

Playgound
Code:

fn main() {
    let tex = Texture { name: "test.png".to_string() };
    let img = Image { tex: &tex };
    let mut gui_handler = GUIHandler { vec: vec![Box::new(MainGUI { img })] };
    let i = InputHandler{};
    i.test(&mut gui_handler,&tex);
}

trait GUI {

}

struct GUIHandler<'a> {
    vec: Vec<Box<dyn GUI + 'a>>
}

struct Texture {
    name: String
}

struct Image<'a>{
    tex : &'a Texture
}


struct MainGUI<'a> {
    img: Image<'a>
}

impl <'a> GUI for MainGUI<'a>{

}

struct InputHandler {

}

impl InputHandler {
    pub fn test(&self, gui_handler:&mut GUIHandler, tex:&Texture) {
        gui_handler.vec.push(Box::new(MainGUI { img: Image { tex } }))
    }
}

I hope you can help and show me what I am doing wrong.

The lifetime parameter to GUIHandler<'a> is specifying the lifetime for which the dyn GUI must be valid and since a MainGUI<'b> containing a &'b Texture is only valid for 'b. This means that when putting a MainGUI<'b> into a GUIHandler<'a> you must make sure that 'b outlives 'a.

Lets include all those implicit (elided) lifetimes in your current function signature for test:

pub fn test<'a, 'b, 'x1, 'x2>(&'x1 self, gui_handler:&'x2 mut GUIHandler<'a>, tex:&'b Texture) 

then by adding 'b outlives 'a, i.e. 'b: 'a, we can make it compile:

pub fn test<'a, 'b, 'x1, 'x2>(&'x1 self, gui_handler:&'x2 mut GUIHandler<'a>, tex:&'b Texture) 
where 'b: 'a
{
    gui_handler.vec.push(Box::new(MainGUI { img: Image { tex } }))
}

By the way, we also need 'x2: 'a, since a reference may not be valid/alive for longer than the type it references, but the compiler actually invisibly automatically adds 'x2: 'a to this function and every other function that uses it.


Finally, references &'c T can be coerced into &'d T whenever 'd is a smaller lifetime than 'c, i.e. 'c outlives 'd, i.e. 'c: 'd. Thus in our case where with the function signature above, we always need to pass a &'b Texture to test together with a &GuiHandler<'a> where 'b: 'a, the &'b Texture could be actually be coerced into ``&'a Texture` at the call site. Hence restricting the function signature even more to

pub fn test<'a, 'b, 'x1, 'x2>(&'x1 self, gui_handler:&'x2 mut GUIHandler<'a>, tex:&'a Texture) 

doesn’t really make this function any less general, so that’s a reasonable thing to do.

Going back to using elided lifetimes, the signature finally becomes

pub fn test<'a>(&self, gui_handler:&mut GUIHandler<'a>, tex:&'a Texture)

so that’s probably what you want to write here :smiley:


Edit:

Additional note: I’m not sure if keeping a reference like the tex field in your struct is a good idea at all. Since the reference is currently shared/immutable anyways, you might want to try changing it to a reference-counting pointer, like tex: Rc<Texture>. This way you get rid of all those lifetime parameters in your structs without any big disadvantages.

⟶ example code for how to change to using Rc

3 Likes

Thank you very much. This helps me to understand the topic a bit better. I will also try the refernce-counting pointer. Thank you.

I second that Rc. In normal Rust you almost never use temporary references in structs. It makes whole structs temporary themselves, and permanently anchored to a single scope. References, despite the name, are not for storing things by reference! Their primary function is borrowing and preventing objects from moving, which is very restrictive for structs.

Structs with references are great for wrapping parts of a buffer so you don't have to copy a bunch of bytes around while you're processing them. They're not so great for making things like long-lived tree structures or attached event handlers.

1 Like

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.