Help with Lifetime and termion [solved]

Hey I’m new to this Language and I’m trying to learn it by programming a Game. Currently I’m struggling with Lifetimes. Here is the Code which gives me headaches:

struct Game<'o> {
    stdout: termion::raw::RawTerminal<std::io::StdoutLock<'o>
}

fn init<'g>() -> &<'g> Game {
    let stdout = stdout();
    let mut stdout = stdout.lock().into_raw_mode().unwrap();

    &Game {
        stdout
    }
}

And here is the Error:

error[E0106]: missing lifetime specifier
   --> src/main.rs:101:22
    |
101 | fn init<'g>() -> &'g Game {
    |                      ^^^^ expected lifetime parameter
    |
    = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
    = help: consider giving it a 'static lifetime

I think this might has something to do with Subtyping but I wasn’t able to exactly find out what to do.
The only Language I’m very good in is Python which already made me struggle a lot of time, so any help and explanation is welcome :slight_smile:

No, this is not a subtyping error. The problem highlighted is that Game needs its lifetime parameter to be specified (like Game<'g>), and you’re specifying the lifetime of the reference to it instead.

More importantly, you’re trying to return a reference to a function-local variable (The temporary created by Game { stdout }), which will never work. You should return an owned Game or allocate your object outside of the function’s scope instead (See https://doc.rust-lang.org/book/second-edition/ch04-00-understanding-ownership.html)

Also, I’m not sure how stdout() works, but I don’t think there’s anyway to write an init function like this? There’s no way for the caller to specify a lifetime for Game that will be compatible with the return value of stdout(). (Be aware that the caller of the function is the one who chooses the lifetime parameters, usually by passing arguments. Here, you want stdout() to give you a lifetime, but you probably need to tell it what lifetime to give, and you don’t know that…) So it seems you’d have to pass the stdout object into the function, but that might make the whole thing moot. How to solve that really depends on how you’re trying to design the program.

Personally, I would either pass the stdout lock into the init function as an argument or not store it in Game and instead pass it into the methods that actually use it.

@skysch explained the issue with your code, and I’d like to offer up an alternative way to design your type here. In essence:

use std::io::Write;
use termion::raw::IntoRawMode;

struct Game<W: Write> {
    // `RawTerminal` just needs something that's `std::io::Write`
    inner: termion::raw::RawTerminal<W>,
}

impl<W: Write> Game<W> {
    fn new(w: W) -> Self {
        Self {
            inner: w.into_raw_mode().unwrap(), // consider returning `Result` instead of unwrapping
        }
    }
}

fn main() {
    // Now, when it comes time to use your `Game`, get a handle to stdout and pass it in.
    // Being generic over `std::io::Write` will allow you to unit test `Game` easier,
    // with some mocked up impl of `Write`
    let stdout = std::io::stdout();
    let stdout = stdout.lock();
    let g = Game::new(stdout);
}

So this is “dependency injection”, in a nutshell. If you don’t have a specific reason to make your Game work only with stdout, then skip that step and stay generic.

@skysch thank you very mutch for the explanation.
@vitalyd thank you for the example for the explanation.

You two helped me a lot understanding this Problem. I already adapted that Answer and it works!

How do I mark this thread as solved?