hcf
hcf

Reputation: 148

Cannot define appropriate rust lifetime requirements

TL;DR Having problems with lifetime with nested objects. Code is below.

Long Version:

I'm writing a multiplayer game using ggez, and I'm trying to create an abstraction layer for the input (to allow local and remote players play together).

In order to so, I've created an Input trait, and implemented KeyboardInput for local input, which uses ggez keyboard state querying methods.

Now the tricky part: ggez creates Context object at startup, and expects a reference to it in most of the functions that is exposes.

because my KeyboardInput implementation is using ggez input method (specifically, is_key_pressed), it has to pass &Context to this method. However, since the trait itself should be generic, it won't need a Context reference for any other implementation (for example, NetworkInput).

my solution was to add a reference to Context as field in the KeyboardInput struct. However, that caused a lifetime error that I'm still unable to resolve.

I also tried to make the lifetime 'static, but that did not work either.

here is the relevant code:

pub trait Input {
    fn get_direction(&self) -> Direction;
}

pub struct KeyboardInput<'a> {
    left_key: KeyCode,
    right_key: KeyCode,
    _ctx: &'a Context
}

impl KeyboardInput<'_> {
    pub fn new(_ctx: &Context, left_key: KeyCode, right_key: KeyCode) -> KeyboardInput {
        KeyboardInput{
            _ctx,
            left_key,
            right_key
        }
    }
}

impl Input for KeyboardInput<'_> {
    fn get_direction(&self) -> Direction {
        if ggez::input::keyboard::is_key_pressed(self._ctx, self.left_key) {
            return Direction::Left;
        }
        if ggez::input::keyboard::is_key_pressed(self._ctx, self.right_key) {
            return Direction::Right;
        }
        Direction::Unchanged
    }
}

struct Player {
    angle: f32,
    pos_x: f32,
    pos_y: f32,
    input_manager: Box<dyn Input>,
}

impl <'a>MainState {
    fn new(ctx: &'a Context) -> GameResult<MainState> {

        let kbd_input = KeyboardInput::new(ctx, KeyCode::Left, KeyCode::Right);
        let kbd_input = Box::new(kbd_input);

        let s = MainState { 
            last_update: Instant::now(),
            players: vec![
                Player::new(kbd_input)
            ]
        };
        Ok(s)
    }

}

pub fn main() -> GameResult {
    let cb = ggez::ContextBuilder::new("My game", "ggez");
    let (ctx, event_loop) = &mut cb.build()?;
    let state = &mut MainState::new(&ctx)?;
    event::run(ctx, event_loop, state)
}

and the compiler error:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> src\main.rs:75:25
   |
75 |         let kbd_input = KeyboardInput::new(ctx, KeyCode::Left, KeyCode::Right);
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the impl at 72:7...
  --> src\main.rs:72:7
   |
72 | impl <'a>MainState {
   |       ^^
note: ...so that reference does not outlive borrowed content
  --> src\main.rs:75:44
   |
75 |         let kbd_input = KeyboardInput::new(ctx, KeyCode::Left, KeyCode::Right);
   |                                            ^^^
   = note: but, the lifetime must be valid for the static lifetime...
   = note: ...so that the expression is assignable:
           expected std::boxed::Box<(dyn input_manager::Input + 'static)>
              found std::boxed::Box<dyn input_manager::Input>

Upvotes: 0

Views: 158

Answers (1)

Freyja
Freyja

Reputation: 40934

This error almost always means that you are trying to store a value in a way where it can exist longer than it's lifetime.

Let's look at a specific piece of code, annotated with explicit types and lifetimes:

impl<'a> MainState {
    fn new(ctx: &'a Context) -> GameResult<MainState> {
        let kbd_input: KeyboardInput<'a> = KeyboardInput::new(ctx, KeyCode::Left, KeyCode::Right);
        let kbd_input: Box<dyn Input + 'a> = Box::new(kbd_input);

        let s: MainState = MainState {
            last_update: Instant::now(),
            players: vec![
                Player::new(kbd_input as Box<dyn Input + 'static>)
            ]
        };
        Ok(s)
    }
}

On line 9, you're trying to assign kbd_input to Box<dyn Input>. But Box<dyn Input> has no explicit lifetime, it is implicitly equivalent to Box<dyn Input + 'static>. So you're trying to assign a value with lifetime 'a to a type with a static lifetime, which is not allowed.

The solution is to explicitly set the lifetime of the trait object type: Box<dyn Input + 'a>. This cascades and means that you'll need to add a lifetime to the MainState struct as well, as it now will contain a type with a non-static lifetime:

struct MainState<'a> {
  /* ... */
  players: Vec<Box<dyn Input + 'a>>,
}

Upvotes: 2

Related Questions