Skip to content

Errors

You now have a tui module that restores the state of the terminal at the end of main.

But in addition to that, you want to make sure that even when the application panics, you restore the state of the terminal back to a normal working state. You will also want to print the error to the terminal to that the user can see what went wrong.

Rust has a built-in function called [set_hook] to set a panic hook. Additionally, color_eyre has some ready to install hooks to you can leverage for better panics.

Putting that together along with restoring the terminal backend state might look something like this:

let (panic_hook, _) = color_eyre::config::HookBuilder::default().into_hooks();
let panic_hook = panic_hook.into_panic_hook();
std::panic::set_hook(Box::new(move |panic_info| {
if let Err(err) = crate::tui::restore() {
log::error!("Unable to restore terminal: {err:?}");
}
panic_hook(panic_info);
}));

You can customize the output of the panic hook in a number of different ways. For example, with human-panic, you can autogenerate a log file that contains the stacktrace that a user can submit to you for further investigation.

Here’s the code using color_eyre to set a panic hook. Put the contents of this file into src/errors.rs:

src/errors.rs
use color_eyre::{
config::{EyreHook, HookBuilder, PanicHook},
eyre::{self, Result},
};
use crate::tui;
pub fn install_hooks() -> Result<()> {
let (panic_hook, eyre_hook) = HookBuilder::default()
.panic_section(format!(
"This is a bug. Consider reporting it at {}",
env!("CARGO_PKG_REPOSITORY")
))
.into_hooks();
install_color_eyre_panic_hook(panic_hook);
install_eyre_hook(eyre_hook)?;
Ok(())
}
fn install_color_eyre_panic_hook(panic_hook: PanicHook) {
let panic_hook = panic_hook.into_panic_hook();
std::panic::set_hook(Box::new(move |panic_info| {
if let Err(err) = tui::restore() {
println!("Unable to restore terminal: {err:?}");
}
panic_hook(panic_info);
}));
}
fn install_eyre_hook(eyre_hook: EyreHook) -> color_eyre::Result<()> {
let eyre_hook = eyre_hook.into_eyre_hook();
eyre::set_hook(Box::new(move |error| {
tui::restore().unwrap();
eyre_hook(error)
}))?;
Ok(())
}

Let’s update main.rs to the following:

src/main.rs
mod crates_io_api_helper;
mod errors;
mod tui;
#[tokio::main]
async fn main() -> color_eyre::Result<()> {
errors::install_hooks()?;
let mut tui = tui::init()?;
tui.draw(|frame| {
frame.render_widget(
ratatui::widgets::Paragraph::new("hello world"),
frame.size(),
);
// panic!("Oops. Something went wrong!");
})?;
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
tui::restore()?;
Ok(())
}

Your file structure should now look like this:

.
├── Cargo.lock
├── Cargo.toml
└── src
├── crates_io_api_helper.rs
├── errors.rs
├── main.rs
└── tui.rs