Using references to object in closure stored within object itself

Hi! I'm a beginner and I'm trying to create a TUI in order to learn Rust.

I'm now implementing an event handler that stores closures that handle the terminal events.

use rustier::Terminal;
use rustier::KeyCode;

/// Entry point.
fn main() {
    let mut terminal = rustier::init();

    register_events(&mut terminal);
}

/// Register the events.
fn register_events(terminal: &mut Terminal) {
    // Exit when pressing ESC.
    terminal.event_handler.register_key(KeyCode::Esc, Box::new(|_| {
        rustier::quit(terminal);
        std::process::exit(0);
    }));
}

If I try to compile this code I receive this error from the compiler:

error[E0621]: explicit lifetime required in the type of `terminal`
  --> src/main.rs:21:55
   |
19 |   fn register_events(terminal: &mut Terminal) {
   |                                ------------- help: add explicit lifetime `'static` to the type of `terminal`: `&'static mut rustier::Terminal`
20 |       // Exit when pressing ESC.
21 |       terminal.event_handler.register_key(KeyCode::Esc, Box::new(|_| {
   |  _______________________________________________________^
22 | |         rustier::quit(terminal);
23 | |         std::process::exit(0);
24 | |     }));
   | |______^ lifetime `'static` required

This is the program's entry point. It creates a new instance of rustier::Terminal ("Rustier" is the name of the TUI library) and then proceeds to register an event which will cause the program to exit when the ESC key is pressed.

Now, I'm aware that this code has several issues, but I'm trying to keep it as simple as possible so that hopefully someone can help me understand the issues with it and which changes should be applied in order to overcome them.

I'm aware, fox example, that I can't use terminal like that inside the closure in the register_events function since the closure itself is stored within the event handler which is stored inside a rustier::Terminal. I think that I should probably use a reference/pointer inside the closure but I'm having a hard time understanding which one.

Thanks for the help :slight_smile:

Why can't I find the documentation or source for rustier? It would really help to be able to see the types used in the interface.

  1. You get only temporary access to terminal via &mut borrow, which you are only allowed to use until the end of this function call, and not any longer. So you can't put that temporary reference in any place that could outlive this function.

  2. The borrow checker will never allow a struct that contains a reference to itself. This is not safe under borrow checking rules.

You will need Arc<Terminal> or Arc<Mutex<Terminal>> to make it self-referential. Alternatively, you can use *mut Terminal and unsafe if you're sure the self-reference will be valid long enough. Note that it may not be actually safe, since let variables don't have stable addresses. You'd need at least Box<Terminal> to make it safe-ish.

The source is available here but I have made changes after the last commit so it's not updated yet.

1 Like

I see, thanks for the help. I thought about wrapping terminal with a Box but couldn't make it work. Will try your suggestion otherwise will have to re-think about my implementation.

What is the reason for this additional wrapper function (register_event())? Are you planning to expose it somewhere outside of current module?

use rustier::Terminal;
use rustier::KeyCode;

/// Entry point.
fn main() {
    let mut terminal = rustier::init();

    // Register the events.
    terminal.event_handler.register_key(KeyCode::Esc, Box::new(|_| {
        rustier::quit(terminal);
        std::process::exit(0);
    }));
}

If you really want to have extension method - extend rustier module by implementing register_event(&mut self)...

Box always has a single owner (exists in one place only). You need Arc for terminal to be able to exist in more than one place.

I understand. And since I need to make changes to terminal then I need a Arc<Mutex<Terminal>>> right?

Well if you want to share and mutate it, you need an Arc<Mutex<...>> yes, although if you can find a way to do it without the need to share it, that would likely make your code simpler.

If you control implementation of Terminal, you can pass self to the callbacks. This way the callbacks won't need to be self-referential.

When passing self to callbacks, you will need to ensure that they're all shared borrows, or that if the callback functions are mutable, they're separate from self (e.g. cloned Arc<Mutex<dyn FnMut>>), because self.callbacks[x](self) uses self twice.