setjmp/longjmp is used for no-local jumps, which means to ‘jump’ between different functions.
setjmp is used to declare a place (which is identified by an integer id) to jump to, while longjmp actually jumps to a place with a value. Generally if setjmp returns an value, it means that a no-local jump has occurred at this place.
This technique is generally used for exception handling, it allows the program to kind of ‘rollback’ through the stack to some point the exception not has happened. Complex and dangerous (unexpected behaviour & memory leaks) as these functions are, they are avoided mostly in modern code bases.
Recently I am using libjpeg-turbo bindings in Rust, which uses this technique for error handling. I’d like to share some experiences about dealing with libjpeg in Rust.
Error handling in Libjpeg
Libjpeg uses a jpeg_err_mgr struct for error handling related stuff. The struct contains error messages, code, stack and a pointer named error_exit to a callback function. You could call jpeg_std_err() to initialize the struct.
If you do not assign a function to error_exit, libjpeg will terminate the process when error occurs. In Rust, this means the whole thread will shut down. You could use the catch_unwind feature which is added to stable channel recently.
However, catch_unwind will try to restore the thread with its stack, which could slow down the program. Also it’s not recommanded for a general try/catch situation. A better way is to use setjmp/longjmp in Rust FFI.
No-local jump in FFI
First we should declare extern C binding for the two functions.
No-local jump needs a buffer to store the stack. Create a struct to store the buffer. We will name it my_error_mgr. To make this easier, the struct is not handling error messages.
Next step is to create a function for error handling, bind it to the err_mgr. The handler will simply jump with value 1.
1 2 3 4 5 6 7
// declare the function somewhere before extern"C"fnlibjpeg_error_handler(c_info: &mut jpeg_common_struct) { let my_err: *mut my_error_mgr = c_info.err as *mut my_error_mgr; unsafe { longjmp((*my_err).setjmp_buffer, 1); } }