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.
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.
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.
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.
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.