Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the best way to register a function to run during an unexpected exit of a Rust program?

Tags:

exit

rust

atexit

I'm creating a terminal text editor in Rust. The editor puts the terminal into raw mode, disabling character echoing and the like, and then restores the original terminal function upon exit.

However, the editor has some bugs, and crashes unexpectedly every now and again due to issues like unsigned variable underflow. When this happens, the cleanup code which would restore the terminal to its original state never runs.

The cleanup function I'd like to run is the following:

fn restore_orig_mode(editor_config: &EditorConfig) -> io::Result<()> {
    termios::tcsetattr(STDIN, termios::TCSAFLUSH, &editor_config.orig_termios)
}
like image 674
isaacg Avatar asked Oct 21 '25 19:10

isaacg


2 Answers

In the latest stable Rust, @for1096's answer is the best. In your case, it might be quite simple to apply because your clean-up does not need to use state that is shared with the application code:

use std::panic::catch_unwind;

fn run_editor(){
    panic!("Error!");
    println!("Running!");
}

fn clean_up(){
    println!("Cleaning up!");
}

fn main(){
    match catch_unwind(|| run_editor()) {
        Ok(_) => println!("Exited successfully"),
        Err(_) =>  clean_up()
    }
}

If your clean-up requires accessing shared state with your application, then you will need some additional machinery to convince the compiler that it is safe. For example, if your application looks like this:

// The shared state of your application
struct Editor { /* ... */ }

impl Editor {
    fn run(&mut self){
        println!("running!");
        // panic!("Error!");
    }

    fn clean_up(&mut self){
        println!("cleaning up!");
    }

    fn new() -> Editor {
        Editor { }
    }
}

Then, in order to call clean_up, you would have to manage access to the data, something like this:

use std::panic::catch_unwind;
use std::sync::{Arc, Mutex};

fn main() {
    let editor = Arc::new(Mutex::new(Editor::new()));

    match catch_unwind(|| editor.lock().unwrap().run()) {
         Ok(_) => println!("Exited successfully"),
         Err(_) => {
             println!("Application panicked.");
             let mut editor = match editor.lock() {
                Ok(guard) => guard,
                Err(poisoned) => poisoned.into_inner(),
             };
             editor.clean_up();
         }
    }
}

Prior to Rust 1.9, you can only handle panics that occur in a child thread. This isn't much different except that you need to clone the Arc because the original one will need to be moved into the thread closure.

use std::thread;
use std::sync::{Arc, Mutex};

fn main() {
    let editor = Arc::new(Mutex::new(Editor::new()));
    // clone before the original is moved into the thread closure
    let editor_recovery = editor.clone();

    let child = thread::spawn(move || {
         editor.lock().unwrap().run();
    });

    match child.join() {
        Ok(_) => println!("Exited successfully"),
        Err(_) => {
            println!("Application panicked.");
            let mut editor = match editor_recovery.lock() {
                Ok(guard) => guard,
                Err(poisoned) => poisoned.into_inner(),
            };
            editor.clean_up();
        }
    }
}
like image 99
Peter Hall Avatar answered Oct 23 '25 11:10

Peter Hall


Try catch_unwind. I haven't used it, so I cannot guarantee it works.

like image 20
for1096 Avatar answered Oct 23 '25 10:10

for1096



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!