Lifetime problem with traits

I've run into a lifetime problem, which doesn't seem to have a solution that won't involve polluting future implementations of a trait with explicit inferred lifetime parameters.

The problem is that I have two implementations of an update trait; App which requires extra explicit lifetime bounds and Simple whose lifetime bounds can be inferred by the compiler. But because of App's impl requirements I need to explicitly add inferred bounds to Simple's implementation. So, my question is, is there a way to implement Update for App that doesn't require that I pollute the rest of my library with lifetime bounds.

I've included the full playground below with a simplified version of my program.

struct Buffer<'a> {
   data: &'a mut i32,
}

struct Keymap<T> {
   func: fn(&mut T),
}

struct App<'borrow, 'short, 'buffer> {
   keymap: &'borrow mut Keymap<BufferAndUi<'short, 'buffer>>,
   buffer: &'borrow mut Buffer<'buffer>,
}

struct Ui<'ctx> {
   ctx: &'ctx mut f32,
}

impl<'ctx> Ui<'ctx> {
   fn set_id(&mut self, id: f32) {
       *self.ctx = id;
   }

   fn print(&self) {
       println!("{}", self.ctx)
   }
}

struct BufferAndUi<'borrow, 'b> {
   id: i32,
   buffer: &'borrow mut Buffer<'b>,
   ui: &'borrow mut Ui<'b>,
}

trait Update<'a, 'func> {
   fn update(self, ui: &'func mut Ui<'a>);
}

impl<'app: 'inner_borrow, 'inner_borrow, 'buffer, 'func: 'inner_borrow> Update<'buffer, 'func> for App<'app, 'inner_borrow, 'buffer> {
   fn update(self, ui: &'func mut Ui<'buffer>) {
       *ui.ctx = 0.0;
       println!("{}", ui.ctx);
       (self.keymap.func)(&mut BufferAndUi::<'inner_borrow, 'buffer> {
           id: 1,
           buffer: self.buffer,
           ui,
       });
   }
}

struct Simple {
   id: f32,
}

impl Update<'_, '_> for Simple {
   fn update(self, ui: &mut Ui) {
       ui.set_id(self.id);
       ui.print();
   }
}

fn print_value(input: &mut BufferAndUi) {
   *input.buffer.data = 10;
   println!("{} {} {}", input.buffer.data, input.ui.ctx, input.id);
}

fn main() {
   let mut keymap = Keymap { func: print_value };
   let mut buffer = Buffer { data: &mut 0 };
   let app = App {
       keymap: &mut keymap,
       buffer: &mut buffer,
   };

   let mut ui = Ui { ctx: &mut 10.0 };

   app.update(&mut ui);

   let simple = Simple { id: 5.0 };

   simple.update(&mut ui);
}

(Playground)

Wow, that's a lot of lifetimes. I haven't found a "solution" for you yet, instead I think I've spotted a problem:

The implementation

impl<'app: 'inner_borrow, 'inner_borrow, 'buffer, 'func: 'inner_borrow> Update<'buffer, 'func> for App<'app, 'inner_borrow, 'buffer>

requires 'app: 'inner_borrow; but the type App<'app, 'inner_borrow, 'buffer> contains &'app mut Keymap<BufferAndUi<'inner_borrow, 'buffer>>, which comes with an implicit 'inner_borrow: 'app requirement. In effect, 'app an 'inner_borrow are forced to be the same lifetime here; this means that you're dealing with a &'app mut Keymap<BufferAndUi<'app, 'buffer>> pattern where a value is mutably borrowed forever / for its entire existence. That's usually an anti-pattern.

In any case, if that's all working as intended and the borrowing-a-value-forever thing is not a problem, equating 'app and 'inner_borrow explicitly at least simplifies the impl syntactically to

impl<'app, 'buffer, 'func: 'app> Update<'buffer, 'func> for App<'app, 'app, 'buffer> {
    fn update(self, ui: &'func mut Ui<'buffer>) {
        *ui.ctx = 0.0;
        println!("{}", ui.ctx);
        (self.keymap.func)(&mut BufferAndUi::<'app, 'buffer> {
            id: 1,
            buffer: self.buffer,
            ui,
        });
    }
}
1 Like

Thanks for the reply! I clearly don't understand lifetimes as well as I thought I did :sweat_smile:. But based on your explanation if I want 'inner_borrow to live for only a small subset of 'app it is not possible to implement Keymap with its current design due to the implicit lifetime rule invoked by &'app mut Keymap<BufferAndUi<'inner_borrow, 'buffer>>.

Just some general advice about something I noticed. In your multi-lifetime structs, your lifetime orders go from shortest to longest (e.g. BufferAndUi<'borrow, 'b> contains &'borrow mut Ui<'b>), but in your trait the order is longest to shortest. You might want to make them consistent, as it will then be easier to spot things like:

// N.b. rewritten so `Update` is also in shortest to longest order
impl<
    'app: 'inner_borrow, // Says app is longer than inner_borrow
    'inner_borrow,       // 
    'buffer,             // But the order implies the opposite
    'func: 'inner_borrow //      vvvvvvvvvvvvvvvvvvvv 
> Update<'func, 'buffer> for App<'app, 'inner_borrow, 'buffer> {

There's a workaround to the "lifetime pollution". You can have a simpler trait, and a blanket implementation to narrow the simple trait into the specific trait:

trait SimpleUpdate {
    fn simple_update(self, ui: &mut Ui<'_>);
}

// My trait lifetimes are still opposite of yours in this example
impl<'borrow, 'buffer, T> Update<'borrow, 'buffer> for T where T: SimpleUpdate {
    fn update(self, ui: &'borrow mut Ui<'buffer>) {
        self.simple_update(ui)
    }
}

Then you can

impl SimpleUpdate for Simple {
    fn simple_update(self, ui: &mut Ui<'_>) {
        ui.set_id(self.id);
        ui.print();
    }
}

And you'll get Update "for free". On the other hand, you now have more traits and methods to keep track of, so I'm not sure it's a net improvement.

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.