`Rc<RefCell<>>` and drop check

Hi,

I'm new to Rust and while I think I understand lifetimes more or less, I'm really struggling with a case I have in my application.

I'm trying to implement a relatively simple UI using Rust and SDL2. SDL2 provides a Texture struct that implements Drop trait. A single Texture is shared by multiple Sprite structs that represent a part of the whole texture. I need the texture to be mutable, so I don't pass it as an ordinary reference, I use Rc<RefCell<Texture<'a>>> instead. I keep all the Sprite structs within a SpriteCollection. Finally, a Widget struct, representing buttons and other drawable UI elements, is created while being passed a reference to SpriteCollection so it can access sprites. Simplified code looks like this:

use core::marker::PhantomData;
use std::rc::Rc;
use std::cell::RefCell;

// external library, I can't change this
struct Texture<'a> {
    state: i32,
    _marker: PhantomData<&'a ()>
}

impl<'a> Drop for Texture<'a> {
    fn drop(&mut self) {}
}
// end of external library

struct Sprite<'a> {
    texture: Rc<RefCell<Texture<'a>>>
}

struct SpriteCollection<'a> {
    sprites: Vec<Sprite<'a>>
}

impl<'a> SpriteCollection<'a> {
    fn new(texture: Texture<'a>) -> Self {
        let texture = Rc::new(RefCell::new(texture));
        let sprite = Sprite { texture: texture.clone() };
        let sprites = vec![sprite];
        SpriteCollection { sprites }
    }
    
    fn sprite(&self, i: i32) -> &Sprite<'a> {
        &self.sprites[i as usize]
    }
}

struct Widget<'a> {
    sprite: &'a Sprite<'a>
}

impl<'a> Widget<'a> {
    fn new(sprite_collection: &'a SpriteCollection<'a>) -> Self {
        Widget { sprite: sprite_collection.sprite(0) }
    }
}

fn main() {
    let texture = Texture { state: 0, _marker: PhantomData };
    let sprite_collection = SpriteCollection::new(texture);
    let _widget = Widget::new(&sprite_collection);
}

When I try to compile the above I get the error:

error[E0597]: `sprite_collection` does not live long enough
  --> src/main.rs:50:31
   |
50 |     let _widget = Widget::new(&sprite_collection);
   |                               ^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
51 | }
   | -
   | |
   | `sprite_collection` dropped here while still borrowed
   | borrow might be used here, when `sprite_collection` is dropped and runs the destructor for type `SpriteCollection<'_>`

I suppose this is caused by a drop check. But I don't understand why it happens - compiler's message suggests that some code may access sprite_collection after it's dropped, but I don't see how. If I remove the Drop trait from Texture, the above snippet compiles, but I can't do this in actual application.

I'd really appreciate it if anyone could explain the problem, so that I can work around it.

Snippet can be found here:

By reusing the same lifetime, you require both lifetimes to be equal, however the inner lifetime is not able to be extended (or the outer shortened) for this to be the case. This compiles:

struct Widget<'a, 'b> {
    sprite: &'a Sprite<'b>
}

impl<'a, 'b> Widget<'a, 'b> {
    fn new(sprite_collection: &'a SpriteCollection<'b>) -> Self {
        Widget { sprite: sprite_collection.sprite(0) }
    }
}

playground

3 Likes

It works, thank you!