Search code examples
sfmlrustlifetimelifetime-scoping

Rust (+SFML) - How to avoid extra object construction when limited by lifetime parameters?


I'm using rust-sfml (rsfml::graphics) to, at the moment, draw pixels to the screen. (I'm just starting with both Rust and the project.) I'm storing the data in an Image, then copying to a Texture.

This Texture is used to create a Sprite<'s>; herein lies my problem. I need to be able to mutate the Texture, but the type of Sprite<'s> seems to guarantee that I can't do what I want. Since I need to be able to call window.draw(&sprite) every time the window is redrawn, I'm just creating a new Sprite each time.

The preferable alternative would keep the Sprite<'s> in my struct Render along with the Texture. Since 'Sprite' has a lifetime parameter, it becomes struct Render<'s>:

struct Render<'s> {
    texture: Texture,
    sprite: Sprite<'s>,
}

I have a method on Render:

fn blit(&'s mut self) -> ()

which mutates the Render (by editing the Texture). Now, as soon as I try to call blit more than once, I run into this problem:

render.blit();
render.blit();  // error: cannot borrow `render` as mutable more than once at a time

which, I think, happens because the lifetime parameter forces the the Render's lifetime-as-borrowed-by-the-first-blit-call to be equal to the lifetime of the Render instance (the whole main function).

How can I keep my original Sprite and continue to be able to mutate the container? Is it possible?

Here is a stupid-looking and fairly minimal example:

extern crate rsfml;

use rsfml::graphics::Sprite;

fn main() -> () {
    let mut render = Render::new();
    render.blit();
    render.blit();  // error: cannot borrow `render` as mutable more than once at a time
}

struct Render<'s> {
    sprite: Option<Sprite<'s>>,
}

impl<'s> Render<'s> {
    fn new() -> Render { Render { sprite: None } }
    fn blit(&'s mut self) -> () { }
}

(Apologies if the question is not clear. It's difficult to express when I'm not very familiar with the concepts.)


Solution

  • When you call blit, there are two lifetimes under consideration; self, you see, is of type &'ρ₀ Render<'ρ₁> for certain lifetimes ρ₀ and ρ₁. In your impl<'s> Render<'s> declaration, you have declared that ρ₁ is 's, and in your &'s mut self you have declared that ρ₀ is 's: thus, the lifetime of the borrow of self is 's, which means that you can only ever have one borrow as it will survive until the destruction of the type—it has been declared as “at least as long as the type being referred to”.

    You want to alter this to introduce a new lifetime parameter for the blit function which is permitted to be less than 's; you wish ρ₀ to be minimal, only tied to the return value (as I presume you are actually using 's—if not, you should just omit it and allow the compiler to infer what we’re about to write explicitly). This way, the borrow will only be active while the return value of the function is still in scope (in your trivial example, it’s not used and so you can immediately take another reference).

    This, then, is the alteration to the function that needs to be done:

    fn blit<'a>(&'a mut self) { }
    

    If you alter this to have a return value and use it, you’ll need to make sure that it is out of scope before you call blit again. That may mean passing it directly to a function, or it may mean introducing a new scope, like this:

    {
        let foo = render.blit();
        // … do things with foo … it then gets freed at the end of the block.
    }
    render.blit();