What is wrong with my lifetimes?

Hello. This is a continuation of the question I asked here.

I am trying to write a Battleship game using FLTK. The feature I'm currently trying to implement is moving ships onto the board. When I click a ship, I want to let some structure know that the whole game is in the state of "moving ship x". Then, I can have the app listen for mouse move events. The listener will check the game state, and if it is "moving ship x", then it will update the x and y position of ship x.

Here is the code that I believe is relevant right now:

view.rs:

struct Board
{
    grp: Group,
}

impl Board
{
    pub fn new(x: i32, y: i32) -> Self
    {
      // ...
      let mut ships = ArrayVec::<Frame, 6>::new(); // 'a
      // Now, make the ships
      for n in 0..6
      {
          // 5px was added to the y pos, and 10px taken off the hight of the ship for vertical padding reasons. 
          let mut dummy_ship = Frame::new(350, 55 + ((n as i32 + 1) * SQUARE_SIZE), (n as i32 + 1) * (SQUARE_SIZE ), SQUARE_SIZE - 10, "");
          dummy_ship.set_frame(FrameType::OvalBox);
          dummy_ship.set_color(Color::from_u32(0xE9ECF0));
          ships.push(dummy_ship);
      }
      // elements of ships have at least lifetime 'a, since ownership was given to the vector

      // make the model for moving ships
      let model = Rc::new(RefCell::new(Model::new())); // 'b

      // now set the callbacks
      for s in ships 
      {
          let m = Rc::clone(&model);
          s.set_callback(move |s| {
              let mut m = m.borrow_mut();
              match m.game_boat_state {
                  BoatState::Placed => {
                      m.game_boat_state = BoatState::Moving(s);
                  }
                  BoatState::Moving(_) => {
                      m.game_boat_state = BoatState::Placed;
                  }
              }
          });
      }
      // ...
    }
  }

model.rs

use fltk::frame::Frame;

// Now we need to keep track of whether our boats are being moved on the screen
// or not.
pub enum BoatState<'a>
{
    Placed,
    Moving(&'a mut Frame),
}

pub struct Model<'a>
{
    pub game_boat_state: BoatState<'a>,
}

impl<'a> Model<'a>
{
    pub fn new() -> Self
    {
        Self
        {
            game_boat_state: BoatState::Placed,
        }
    }

}

And here is the compiler error that I get:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
   --> src/battleship_game/view.rs:143:45
    |
143 |                         m.game_boat_state = BoatState::Moving(s);
    |                                             ^^^^^^^^^^^^^^^^^
    |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the body at 139:28...
   --> src/battleship_game/view.rs:139:28
    |
139 |               s.set_callback(move |s| {
    |  ____________________________^
140 | |                 let mut m = m.borrow_mut();
141 | |                 match m.game_boat_state {
142 | |                     BoatState::Placed => {
...   |
148 | |                 }
149 | |             });
    | |_____________^
note: ...so that reference does not outlive borrowed content
   --> src/battleship_game/view.rs:143:63
    |
143 |                         m.game_boat_state = BoatState::Moving(s);
    |                                                               ^
    = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `[closure@src/battleship_game/view.rs:139:28: 149:14]` will meet its required lifetime bounds
   --> src/battleship_game/view.rs:139:15
    |
139 |             s.set_callback(move |s| {
    |               ^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0495`.

Here is how I understand the situation.

My model, at any time, may contain a reference to a Frame. If I create an instance of my model and refer it to some Frame, then in order for my code to compile, I have to prove to the compiler that said Frame is going to be valid for at least as long as the model.

The reason that I think my code should work is that I have created all of my frames before I have created the model that can refer to them, and I have created them in the same scope (which is the Board constructor). Sure, the model is actually owned by a Reference Counter. But all of the Rc's that point to my model are owned either by a) the closure in each Frame's set_callback or b) the Board constructor. Because of this, isn't it valid to assert that in this particular case, the model is alive only when any of the frames it may refer to are also alive?

For the full code, here is the repo: Ubspy/rust-battleship at drag-boats (github.com)

Annotating your structs with lifetime annotations is very unlikely to work. Storing references is a bad idea. You could use an Rc instead.

Generally, any use of callbacks forces anything shared across the callback boundary to be wrapped in an Rc.

1 Like

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.