0
votes

I'm writing a simple game in Rust, which includes a Drawer, which is constructed once and then passed through many methods as a mutable reference:

pub struct Drawer<'a> {
    // ...
    renderer: Renderer<'a>,
    // ...
}

fn pause(drawer: &mut Drawer, events: &[Event]) {
    // ...
    drawer.draw_text(&TextPos::Centered, "Paused", 1);
    // ...
}

I wanted to refactor my code to introduce a fluent interface for drawing text, such as:

drawer.text()
    .size(4)
    .centered()
    .draw("Paused");

I did this by creating a TextDrawer struct, that contains a reference to the Drawer:

pub struct TextDrawer<'a, 'b: 'a> {
    pos: TextPos,
    size: u32,
    drawer: &'a mut Drawer<'b>,
}

impl<'a> Drawer<'a> {
     pub fn text(&mut self) -> TextDrawer {
        TextDrawer {
            pos: TextPos::At(0, 0),
            size: 1,
            drawer: self,
        }
    }
}

I think the lifetimes I've put on the struct are correct (the reference must last as long as the Drawer itself).

However, my text method will not compile until I add explicit lifetimes. When I do, every method that calls text then demands explicit lifetimes, and so on. I'm surprised these lifetimes have to be stated: after all, they're all of the form fn foo<'a, 'b: 'a>(drawer: &mut'a Drawer<'b>). I had assumed before this point that this was already the inferred lifetime, since it was always necessary for the reference to last as long as the Drawer itself.

Is it necessary for me to put these explicit lifetimes all other my method signatures? Or can I avoid it in some other way?

1
Not related to your question, but you might wish to consider renaming Drawer to something that doesn't have a more common second meaning. At least, assuming it's meant to be pronounced /drɔ ər/ instead of /dror/ - trentcl
can you post your error? What you've posted doesn't look like it will compile. The return of TextDrawer is possibly inferring the wrong bounds, but that's not clear to me. And drawer: &'a mut Drawer<'b> looks wrong, as you don't need to mut on inner types (mutability is inherited by the reference to the type, and everything is mutable in the Type when a reference to it is). - bluejekyll
@bluejekyll Your last statement is wrong: you can't change anything through a &mut &T (mutable reference to an immutable reference). So the mutable reference inside does make sense (except if I'm missing something crucial here). - Lukas Kalbertodt
I minimized your example and added lifetime parameters to the text() method and it worked. I didn't need to add lifetime parameters to the pause() function for example, that still worked. Could you, Felix, explain your last big paragraph in more detail? Possibly with code and error messages? If my linked solution already helped, please let me know -- I'll make it an answer then. - Lukas Kalbertodt
You appear to be correct that it is not "wrong" though, based on your example, it does look unnecessary. Thanks for correcting me. - bluejekyll

1 Answers

1
votes

Thanks to the minimal example made by Lukas above, I realised I had simply got the lifetime parameters to my text method backwards.

My incorrect lifetimes:

impl<'a> Drawer<'a> {
    pub fn text<'b>(&'b mut self) -> TextDrawer<'a, 'b> {
        // omitted
    }
}

The correct lifetimes:

impl<'a> Drawer<'a> {
    pub fn text<'b>(&'b mut self) -> TextDrawer<'b, 'a> {
        // omitted
    }
}

After this, everything compiles without any further explicit lifetimes.