SDL2 & Borrow checker

I use the SDL2 crate and I wrote those structures:

pub struct TextureMap<'a> {
  textures: Vec<Option<Texture<'a>>>,
  texture_creator: TextureCreator<WindowContext> // SDL2 thing to load textures
}
impl<'a> TextureMap<'a>
{
  pub fn new(texture_creator:  TextureCreator<WindowContext>) -> TextureMap<'a> {
    TextureMap { textures: Vec::new(), filenames: Vec::new(), texture_creator: texture_creator }
  }
  pub fn get_texture(&'a mut self, texture_id: usize) -> &'a Texture {
     // Return the texture or load it from its path (which happens to be known)
     // (...)
    self.textures[texture_id].as_ref().unwrap()
  }
}

In that TextureMap struct, I believe the lifetime specifier is needed as the textures musn't outlive the TextureCreator.

Then I have to propagate that specifier to my SpriteStore structure. Understandable since it aggregates the TextureMap.

pub struct SpriteStore<'a> {
  store: Vec<Sprite>,
  texture_map: TextureMap<'a>
}
impl<'a> SpriteStore<'a>
{
  // Loads the store from JSON files at the start of the game (without loading textures)
  pub fn new(texture_creator: TextureCreator<WindowContext> ) -> Self {
  // ... fills in the structures
  }

  pub fn render(&'a mut self, canvas: &mut WindowCanvas, sprite_id: usize, x: i32, y: i32) {
    let sprite = &self.store[sprite_id];    
    let tex = self.texture_map.get_texture(sprite.texture_id) ;
    let dest_rect = Rect::new(x, y, sprite.src_rect.width(), sprite.src_rect.height());
    canvas.copy(&tex, sprite.src_rect, dest_rect).unwrap();
  }
}

So when I do something as simple as :

  let mut bsc = SpriteStore::new(canvas.texture_creator());
  bsc.render(&mut canvas, UserSprites::RedCircle as usize, 100, 100);

It says bsc is borrowed at the second line (OK), and "borrowed value does not live long enough", pointing me to the end of the main() function :

`bsc` dropped here while still borrowed
   | borrow might be used here, when `bsc` is dropped and runs the destructor for type `SpriteStore<'_>`

I don't understand this. To me the borrow ends after the render() function returns.

There is no lifetime specifier elsewhere in my code. The Sprite structure is just a rectangle with a texture ID, so it's not the problem.

What do I miss ?

Thank you,

This is problematic because the &'a mut self will have to remain borrowed for as long as the returned reference. And &'a mut self desugars as &'a mut TextureMap<'a>, i.e. its own liveness scope. See &mut inputs don't "downgrade" to & - Learning Rust and Borrowing something forever - Learning Rust

TextureMap is self-referential because its textures field references its texture_creator field. You probably don't want to do that, because it will make TextureMap covariant: &'a Struct<'a> and covariance - Learning Rust (@quinedot is getting a lot of references in this post. Definitely an excellent resource!)

As for how to fix it, you need to clearly define ownership in your structures. TextureCreator should not be owned by TextureMap. Instead, TextureMap can borrow TextureCreator, which is owned elsewhere (maybe in main's stack frame, or in your top-most App struct). Then remove the named lifetime from get_texture() and render(), so the caller can choose an appropriately short lifetime.

pub struct TextureMap<'a> {
    textures: Vec<Option<Texture<'a>>>,
    texture_creator: &'a mut TextureCreator<WindowContext> // SDL2 thing to load textures
}

impl<'a> TextureMap<'a>
{
  pub fn new(texture_creator: &'a mut TextureCreator<WindowContext>) -> TextureMap<'a> {
    TextureMap { textures: Vec::new(), filenames: Vec::new(), texture_creator: texture_creator }
  }
  pub fn get_texture(&mut self, texture_id: usize) -> &Texture {
     // Return the texture or load it from its path (which happens to be known)
     // (...)
    self.textures[texture_id].as_ref().unwrap()
  }
}
2 Likes

Thanks for your answer !

My problem is that I use the texture_creator inside get_textures :

let tex = surface.as_texture(self.texture_creator);
// ..
self.textures[texture_id] = Some(tex);

So when calling as_texture : argument requires that '1 must outlive 'a" where '1 is the function input "&mut self" reference lifetime, which we removed, and 'a the whole object lifetime. So it forces me to put back the 'a on the input ref and the old error is back, since I guess it deduces the return type from it.

Maybe I have a big flaw in my logic or in my understanding of the borrow system ; I will take the time to read all those articles.

That's the first time I've seen surface.as_texture() mentioned.

Surface in sdl2::surface - Rust

It takes &'b TextureCreator<T> and returns Result<Texture<'b>, TextureValueError>. The 'b here just refers to 'a in TextureMap<'a> (via &'a mut TextureCreator<WindowContext>), which is fine... but do you really need a mutable reference to the TextureCreator?

Where does surface come from? What does it reference? A full error message would probably be helpful for better understanding.

1 Like

This is the whole code so that you have the full context.

// main() function
  let (_sdl_context, _image_ctx, _video_subsystem, mut event_pump, mut canvas) 
    = init::init_sdl2("Test", 1000, 600);

  canvas.set_draw_color(Color::RGB(0, 255, 255));
  canvas.clear();
  canvas.present();

  let mut tc = canvas.texture_creator();
  let mut bsc = SpriteStore::new(&mut tc);
  bsc.render(&mut canvas, UserSprites::RedCircle as usize, 100, 100); 
  canvas.present(); // that's all for main() 

// My structs
pub struct SpriteStore<'a> {
  store: Vec<Sprite>,
  texture_map: TextureMap<'a>
}

impl<'a> SpriteStore<'a> {
  // Loads the store from JSON files at the start of the application (without loading textures)
  pub fn new(texture_creator: &'a mut TextureCreator<WindowContext>) -> Self {
    let json_path = "/tmp/meta.json";
    let mut texture_map = TextureMap::new(texture_creator);

    // Vector of spritesheets which are themselves vectors of sprites JSON representations
    let jsheetslist = load_sprites_from_json(json_path);
    let mut store = Vec::new();

    // push Sprite instances in store here (...) 
    
    SpriteStore { store, texture_map }
  }
  
  pub fn render(&'a mut self, canvas: &mut WindowCanvas, sprite_id: usize, x: i32, y: i32) {
    let sprite = &self.store[sprite_id];
    let tex = self.texture_map.get_texture(sprite.texture_id) ;
    let dest_rect = Rect::new(x, y, sprite.src_rect.width(), sprite.src_rect.height());
    canvas.copy(&tex, sprite.src_rect, dest_rect).unwrap();
  }
}


pub struct TextureMap<'a> {
  textures: Vec<Option<Texture<'a>>>,  // The main texture holder
  filenames: Vec<String>, 
  texture_creator: &'a mut TextureCreator<WindowContext> // SDL2 thing to load textures
}

impl<'a> TextureMap<'a> {
  pub fn new(texture_creator: &'a mut TextureCreator<WindowContext>) -> TextureMap<'a> {
    TextureMap { textures: Vec::new(), filenames: Vec::new(), texture_creator: texture_creator }
  }

  pub fn get_texture(&mut self, texture_id: usize) -> &Texture {
    if !self.textures[texture_id].is_none() {
      let img_path = &self.filenames[texture_id];
      let s = Surface::from_file(Path::new(img_path))
        .unwrap_or_else(|err| { prompt_err_and_panic(/* some error */ });
      
      // Color keying on the surface happening here (but nothing fundamentally useful)
        // ...
        
      let tex = s.as_texture(self.texture_creator)
        .unwrap_or_else(|err| { 
          prompt_err_and_panic(/* some error */);
          }
      self.textures[texture_id] = Some(tex);
    }
    self.textures[texture_id].as_ref().unwrap()
  }
}

I finally made it compile and I think there were two problems :

  1. On get_texture, with 'a specifier removed on &mut self, I had the error
14 | impl<'a> TextureMap<'a>
   |      -- lifetime `'a` defined here
...
21 |   pub fn get_texture(& mut self, texture_id: usize) -> &Texture
   |                      - let's call the lifetime of this reference `'1`
...
44 |       let tex = s.as_texture(self.texture_creator)
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'a`

Solution: Thanks for your remark! The texture_creator borrowed in the struct didn't need to be mutable ... so it becomes :

 texture_creator: &'a TextureCreator<WindowContext> // in pub struct TextureMap<'a>

I'm not sure I understand why this solves this precise error though! Is it because the texture_creator was already borrowed mutably, being a member of the struct, that it couldn't be borrowed again by the as_texture function ? If so, the error message was a bit hard to understand.

  1. Then the following error was back :
error[E0597]: `bsc` does not live long enough
  --> client/src/lib.rs:51:3
   |
50 |   let mut bsc = SpriteStore::new(&mut binding );
   |       ------- binding `bsc` declared here
51 |   bsc.render(&mut canvas, UserSprites::RedCircle as usize, 100, 100);
   |   ^^^ borrowed value does not live long enough

Solution: I also removed the 'a in render, the caller of get_texture. This made sense given your first post.
pub fn render(&mut self, ...)

After that it's all good ! I'm still learning to master these lifetimes stories, but I've got a long way to go!

Thank you very much,

The error has to do with subtyping and variance. The table on this page is really nice. You don't have to memorize it but keep this link in your back pocket when you need to reference it. It tells us that &'a mut T is invariant in T, which means the lifetime in T is rather rigid. It can't "grow or shrink".

I might be misinterpreting some of the details, but this is how I see it:

When you borrow the TextureMap<'a> by calling its get_texture() method, that call to surface.as_texture() is connecting the 'a in TextureMap<'a> to the unnamed lifetime in get_texture(&mut self, ...).

That function signature desugars to:

fn get_texture<'b>(
    self: &'b mut TextureMap<'a>,
    texture_id: usize,
) -> &'b Texture<'a>

In fact, if you copy-paste this desugared signature with the &'a mut TextureCreator<WindowContext> field, the error message wouldn't give you the curious '1 lifetime. It tells you pretty clearly what it's trying to do:

error: lifetime may not live long enough
  --> src/main.rs:22:23
   |
10 | impl<'a> TextureMap<'a> {
   |      -- lifetime `'a` defined here
...
18 |     fn get_texture<'b>(self: &'b mut TextureMap<'a>, texture_id: usize) -> &'b Texture<'a> {
   |                    -- lifetime `'b` defined here
...
22 |             let tex = s.as_texture(self.texture_creator)
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'b` must outlive `'a`
   |
   = help: consider adding the following bound: `'b: 'a`

Adding the suggested 'b: 'a bound won't really help because that's not what we're trying to describe! 'b is supposed to be shorter than 'a! We want 'a: 'b! We can't have that when we are holding &'a mut T and passing it through that as_texture() method.

Recall the signature for Surface::as_texture() and how it connects the lifetime in T.

If you added 'b: 'a anyway, you'll find the same old borrowing forever problem that you started with.


Why does the &'a TextureCreator<WindowContext> field fix it? Have a look at that table again! &'a T is covariant in T! It removes the need for 'b: 'a Now 'b can shrink to the smallest lifetime necessary to satisfy the call.

The reason these variance rules exist is adequately explained in the nomicon link. But there are some other good references that help strengthen my intuition without being so gosh darn formal. Variance in Rust: An intuitive explanation - Ehsan's Blog is one of my personal favorites.

Me too. :smiling_face: