SDL2 - Texture lifetimes

I want to preload textures with SDL2, but I'm struggling understanding lifetimes in this scenario.

use sdl2::image::LoadTexture;
use sdl2::render::{Texture, TextureCreator, WindowCanvas};
use sdl2::video::WindowContext;
use std::collections::HashMap;

pub struct Textures<'a> {
    texture_creator: TextureCreator<WindowContext>,
    textures: HashMap<String, Texture<'a>>,
}

impl<'a> Textures<'a> {
    pub fn new(canvas: &WindowCanvas) -> Textures<'static> {
        Textures {
            texture_creator: canvas.texture_creator(),
            textures: HashMap::new(),
        }
    }
    pub fn load_texture(&'a mut self, path: String) -> Result<(), String> {
        let texture = self.texture_creator.load_texture(path.clone())?;
        self.textures.insert(path, texture);
        Ok(())
    }
}

pub fn main() -> Result<(), String> {
    let sdl_context = sdl2::init()?;
    let video_subsystem = sdl_context.video()?;

    let window = video_subsystem
        .window("sdl2", 800, 600)
        .build()
        .map_err(|e| e.to_string())?;
    let canvas = window.into_canvas().build().map_err(|e| e.to_string())?;
    let mut textures = Textures::new(&canvas);
    textures.load_texture("mycooltexture.bmp".to_string());

    Ok(())
}

This results in:

error[E0597]: `textures` does not live long enough
  --> src\bin\test.rs:35:5
   |
34 |     let mut textures = Textures::new(canvas);
   |         ------------ binding `textures` declared here
35 |     textures.load_texture("mycooltexture.bmp".to_string());
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
...
38 | }
   | -
   | |
   | `textures` dropped here while still borrowed
   | borrow might be used here, when `textures` is dropped and runs the destructor for type `Textures<'_>`

I understand that Texture lifetime is bound to TextureCreator lifetime, but since they belong both to the same struct, how is this a problem here?

Them being part of the same struct is itself the problem -- you're trying to create a self-referencial struct. Follow the links (or start here and read forward) for some explanation on why that's problematic.

Or as another round-about explanation, the borrow checker doesn't distinguish between the stack and the heap (say), and if you had this struct...

+---------------------+
|                     |
| texture_on_stack <--+---+
|                     |   |
| texture_pointer ----+---+
|                     |
+---------------------+

...and you moved it, the pointer would dangle; therefore if you can safely achieve this data scenario with references (which you can with a method like your load_texture) the data structure must somehow be stuck in a state that can never move;[1] being borrowed forever accomplishes that.


  1. to avoid a dangling reference, which is UB ↩︎