Lifetimes in closures and recursive reference types

Hello, I’m trying to write 2D buffer and views into it. The pattern I’m trying to create is functions passed a reference to the view to the buffer or part of it. However I hit a wall concerning the lifetimes, as expected. The problem seems to me that I cannot model that passed reference to the closure ends with the calling function.

pub enum Frame<'a> {
    Buffer(Array3D<Cell>),
    View(&'a Frame<'a>, Rect),
    MutView(&'a mut Frame<'a>, Rect),
}

impl Frame<'_> {
    #[inline]
    pub fn bounds(&mut self, bounds: Rect, content: impl FnOnce(&mut Frame)) -> &mut Self {
        content(&mut Frame::MutView(self, bounds););
        self
    }
}

I tried having two lifetimes in the Frame, ’view and 'data, but that didn’t help.

My understanding is that, the closure content could store the reference somewhere, but it shouldn’t outlive it, but I can’t find a way to tell the compiler that.

 1  error: lifetime may not live long enough
    --> tmaze/src/renderer/mod.rs:360:40
     |
 357 | impl<'a> Frame<'a> {
     |      -- lifetime `'a` defined here
 358 |     #[inline]
 359 |     pub fn bounds(&mut self, bounds: Rect, content: impl FnOnce(&mut Frame<'a>)) -> &mut Self {
     |                   - let's call the lifetime of this reference `'1`
 360 |         let mut frame = Frame::MutView(self, bounds);
     |                                        ^^^^ this usage requires that `'1` must outlive `'a`
     |
     = note: requirement occurs because of a mutable reference to `renderer::Frame<'_>`
     = note: mutable references are invariant over their type parameter
     = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

 2  error[E0499]: cannot borrow `*self` as mutable more than once at a time
    --> tmaze/src/renderer/mod.rs:362:9
     |
 357 | impl<'a> Frame<'a> {
     |      -- lifetime `'a` defined here
 ...
 360 |         let mut frame = Frame::MutView(self, bounds);
     |                                        ----
     |                                        |
     |                                        first mutable borrow occurs here
     |                                        this usage requires that `*self` is borrowed for `'a`

This structure is rewrite of previous code, which used Frame trait and struct FrameView and struct FrameViewMut, but these didn’t suffer from this problem:

pub struct FrameViewMut<'a> {
    frame: &'a mut dyn Frame,
    bounds: Rect,
}

impl FrameViewMut<'_> {    
    pub fn bounds(
        &mut self,
        bounds: Rect,
        content: impl FnOnce(&mut FrameViewMut<'_>),
    ) -> &mut Self {
        content(&mut FrameViewMut {
            frame: self,
            bounds,
        });
        self
    }
}

Which I find particularly weird.

Thanks for any help.

The issue with your enum Frame definition is that there’s nothing preventing someone from swapping out the Frame<'??> through the &mut Frame<'??> contained in it. As such you need to precisely track tha '?? lifetime, which cascades through because each Frame will have a different lifetime.

What you really need to express here is that you want a pointer to a Frame<'??> with any lifetime, something like (pseudocode) &'a mut for<'b> Frame<'b>, but we don’t have such a thing.

The reason the trait object work is that it is the closest thing to such construct: &'a mut dyn Frame is short for &'a mut (dyn Frame + 'a), which is a reference that can point to any type that implements both Frame and has a lifetime of at least 'a. Your Frame<'??> would satisfy both of these requirements, but of course the trait object allows more type than just Frame<'??> along with a potential performance hit due to the dynamic dispatch.

If you’re interested, the reason that at the type-level/borrow-checker-level the trait object works boils down to the fact that the conversion &'a mut (dyn Trait + 'long) to &'a (dyn Trait + 'short) (where 'short can even be as short as 'a) is allowed, even though generally the lifetimes 'short and 'long are in an invariant position and thus would generally not be allowed otherwise.

1 Like

The way to handle this sort of thing without dyn is to get rid of the enum and let views be different types:

pub struct Buffer(Array3D<Cell>);
pub struct View<'a>(&'a Buffer, Rect);
pub struct ViewMut<'a>(&'a mut Buffer, Rect);

In this approach, it isn’t possible to construct views pointing at other views (which is the possibility which creates your lifetime trouble) — instead, when you want to make a view from a view, you just borrow the buffer and make the Rect smaller. That’ll be more efficient, too!

You’ll want to have methods which take a Buffer and return views for the whole thing, and make sure that functions which would have taken enum Frame instead take View or ViewMut.

1 Like

I will probably go with the separate structs method, the recursive type method had a pro, that I could easily track alpha values for ViewMut, but since it’s not that important feature, I will just make it a little bit less ergonomic.

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.