Trouble with struct lifetime that borrows a mutable reference to another struct


#1

I am trying to store a mutable reference to another type (opengl_graphics::glyph_cache::GlyphCache) from within my own struct. This page explains why it needs to be mutable.

This is my first real shot at lifetimes. The first question of course is should this even be done / is there a better way. Any input is appreciated.

The full module code (which does not compile) is located here: https://github.com/johnthagen/rust-belt/blob/glyphe-cache/src/game/mod.rs

The parts of interest are:

// Code has been trimmed for forum post.
use opengl_graphics::glyph_cache::GlyphCache;


/// Stores Game state and all objects that exist.
pub struct Game<'a> {
    window_size: Size,
    glyph_cache: &'a mut GlyphCache<'a>, // THIS IS WHERE A MUTABLE REFERENCE IS STORED.

    player: player::Player,

    /// The bullets that are currently live in the window.
    /// Bullets are removed when their TTL expires.
    bullets: Vec<bullet::Bullet>,
    asteroids: Vec<asteroid::Asteroid>,
    score: i64,
    asteroid_timer: f64,
    asteroid_timer_max: f64,

    /// A flag indicating if the player has lost.
    game_over: bool,
}

impl<'a> Game<'a> {
    // HIGHER UP IN THE PROGRAM, THE GLYPHECACHE IS INITIALIZED AND THEN
    // PASSED INTO THIS CONSTRUCTOR TO BE STORED AND USED.
    pub fn new(window_size: Size, glyph_cache: &'a mut GlyphCache) -> Self {
        Game {
            window_size: window_size,
            glyph_cache: glyph_cache,
            player: player::Player::new(window_size),
            bullets: Vec::new(),
            asteroids: Vec::new(),
            score: 0,
            asteroid_timer: 0.1,
            asteroid_timer_max: 4.0,
            game_over: false,
        }
    }

    /// Draws all current live objects onto the screen as well as the current score.
    fn draw(&mut self, context: Context, graphics: &mut GlGraphics) {
        clear(color::BLACK, graphics);
        for bullet in &self.bullets {
            bullet.draw(context, graphics);
        }
        self.player.draw(context, graphics);
        for asteroid in &self.asteroids {
            asteroid.draw(context, graphics);
        }

        text(color::YELLOW,
             26,
             format!("Score: {}", self.score).as_str(),
             self.glyph_cache,  // THIS IS WHERE THE MUTABLE REFERENCE IS NEEDED.
             context.transform.trans(10.0, 20.0),
             graphics);
    }

When I compiled I get the following error:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
  --> src\game/mod.rs:40:9
   |
40 |         Game {
   |         ^^^^
   |
note: first, the lifetime cannot outlive the lifetime 'a as defined on the body at 39:75...
  --> src\game/mod.rs:39:76
   |
39 |     pub fn new(window_size: Size, glyph_cache: &'a mut GlyphCache) -> Self {
   |                                                                            ^
note: ...so that reference does not outlive borrowed content
  --> src\game/mod.rs:42:26
   |
42 |             glyph_cache: glyph_cache,
   |                          ^^^^^^^^^^^
note: but, the lifetime must be valid for the anonymous lifetime #1 defined on the body at 39:75...
  --> src\game/mod.rs:39:76
   |
39 |     pub fn new(window_size: Size, glyph_cache: &'a mut GlyphCache) -> Self {
   |                                                                            ^
note: ...so that expression is assignable (expected &mut opengl_graphics::glyph_cache::GlyphCache<'_>, found &mut opengl_graphics::glyph_cache::GlyphCache<'_>)
  --> src\game/mod.rs:42:26
   |
42 |             glyph_cache: glyph_cache,
   |                          ^^^^^^^^^^^

#2

I think this is the case of lifetime elision backfiring.

new(window_size: Size, glyph_cache: &'a mut GlyphCache)

This should be

new(window_size: Size, glyph_cache: &'a mut GlyphCache<'a>)

#3

That is, the current code is equivalent to

fn new<'b>(window_size: Size, glyph_cache: &'a mut GlyphCache<'b>) -> Game<'a>

Unfortunately, lifetime elisions works for all places where the lifetime can be, not only for &.


#4

Thanks @matklad! That solved the problem. Where would I have looked to find documentation on this? The lifetime section of the new book doesn’t even have an example of a parameter that is passed in like &'a mut GlyphCache<'a> with two lifetimes annotated for one parameter.

Should I submit an issue to add some discussion about this use case so others would be able to learn it more easily?

cc @steveklabnik


#5

There’s a somewhat similar example at the end of https://doc.rust-lang.org/book/lifetimes.html#examples with BufWriter<'a>.

To understand this better, it is useful (at least for me :slight_smile: ) to think of &'a as a syntactic sugar for Ref<'a>. There’s no difference between lifetimes in references and in type parameters. In a sense, a reference in Rust is a generic type (type constructor) parametrized by a single generic parameter, its lifetime.

Should I submit an issue to add some discussion about this use case so others would be able to learn it more easily?

I wonder if we can deprecate current behavior, which indeed sometimes overshoots, and add an #[elidable] attribute to mark only some generic lifetimes for elision (widely known stuff like Cow or Ref).