Please help understanding the borrow checker logic here

Thank you beforehand for reading this. I have written many many lines of rust, in several projects, but still I am learning about the borrow checker....

The topology is, there is a game scene which owns the texture data for sprites, and the sprites themselves. Each sprite has a reference to one of the textures in the vec owned by the GameScene, and all sprites are owned by the GameScene in a vec.

struct Texture {
    data: String, // VERY BIG DATA, only for this scene, want this dropped when scene drops, also no copies please
}

struct Sprite<'a> {
    texture_ref: &'a Texture,
}

impl Sprite<'_> {
    fn new(texture_ref: &Texture) -> Sprite {
        Sprite { texture_ref }
    }

    fn draw(&self) {
        println!("{}", self.texture_ref.data);
    }
}

struct GameScene<'a> {
    textures: Vec<Texture>,
    sprites: Vec<Sprite<'a>>,
}

impl<'a> GameScene<'a> {
    fn new() -> GameScene<'a> {
        let texture = Texture {
            data: "LOADED TEXTURE -- VERY BIG DATA".to_string(),
        };    
        GameScene {
            textures: vec![texture],
            sprites: Vec::new(),
        }
    }

    fn add_sprite(&'a mut self) {
        let texture: &Texture = self.textures.get(0).expect("NO TEXTURES?");
        let sprite = Sprite::new(texture);
        self.sprites.push(sprite)
    }

    fn draw(&self) {
        for sprite in &self.sprites {
            sprite.draw();
        }
    }
}

fn main() {
    let mut scene = GameScene::new();
    scene.add_sprite();
    scene.draw();
}

(Playground)

If we remove the lifetime identifier 'a in

fn add_sprite(&'a mut self) {

then error is

error: lifetime may not live long enough
  --> src/main.rs:38:9
   |
24 | impl<'a> GameScene<'a> {
   |      -- lifetime `'a` defined here
...
35 |     fn add_sprite(&mut self) {
   |                   - let's call the lifetime of this reference `'1`
...
38 |         self.sprites.push(sprite)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'a`

So put the 'a in add_sprite, to say the life time of self is 'a
(I would have assumed that would have been evident for the bc)

Then the error is

error[E0502]: cannot borrow `scene` as immutable because it is also borrowed as mutable
  --> src/main.rs:51:5
   |
50 |     scene.add_sprite();
   |     ----- mutable borrow occurs here
51 |     scene.draw();
   |     ^^^^^
   |     |
   |     immutable borrow occurs here
   |     mutable borrow later used here

I don't understand why self.add_sprite does not release the mutable borrow after it executes, or why the add_sprite method needs a lifetime identifier for self, or how to make this data setup play nice with the bc, without any copy/clone tricks.

I had similar experiences before, and I just ignored these errors, and created the textures in the main, and passed references to it around in draw methods, or I created an assets map, and passed (copiable clonable) keys to this map and references to the map around. These all worked fine for the bc.

But this time, I wanted to ask why this does not work for the borrow checker, just to understand the borrow checker better, and not just workaround the errors here with 'static or other things (because normally calling a mutable struct method does not cause this kind of behavior).

Is there a simple change to this that would make the BC happy?

Sorry for taking time and space here,
and Thank you.

Ultimately, the problem is that you're trying to make GameScene self-referential. Self-referential means that one field of a struct holds a reference to another field of the same struct (or to the entire struct).

However, it's not possible to write self-referential structs with references. You will need to use an alternative such as storing indexes into textures, or wrapping every texture in an Arc.

4 Likes

I understand that...
In a general case,
cross ref from one struct field to another struct field of same struct is not (self-referential)
in the Borrow Checker's intention to solve for lifetimes,
and not a shortcoming of this special case.

Thank you very much for your swift reply.

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.