Error Handling

Emacs Lisp's error handling mechanism uses non-local exits. Rust uses Result enum. emacs-module-rs converts between the 2 at the Rust-Lisp boundaries (more precisely, Rust-C).

The chosen error type is the Error struct from failure crate:


# #![allow(unused_variables)]
#fn main() {
pub type Result<T> = result::Result<T, failure::Error>;
#}

Handling Lisp Errors in Rust

When calling a Lisp function, it's usually a good idea to propagate signaled errors with the ? operator, letting higher level (Lisp) code handle them. If you want to handle a specific error, you can use error.downcast_ref:


# #![allow(unused_variables)]
#fn main() {
match env.call("insert", &[some_text]) {
    Err(error) => {
        // Handle `buffer-read-only` error.
        if let Some(&Signal { ref symbol, .. }) = error.downcast_ref::<ErrorKind>() {
            let buffer_read_only = env.intern("buffer-read-only")?;
            // `symbol` is a `TempValue` that must be converted to `Value`.
            let symbol = unsafe { Ok(symbol.value(env)) };
            if env.eq(symbol, buffer_read_only) {
                env.message("This buffer is not writable!")?;
                return Ok(())
            }
        }
        // Propagate other errors.
        Err(error)
    },
    v => v,
}
#}

Note the use of unsafe to extract the error symbol as a Value. The reason is that, ErrorKind::Signal is marked Send+Sync, for compatibility with failure, while Value is lifetime-bound by env. The unsafe contract here requires the error being handled (and its TempValue) to come from this env, not from another thread, or from a global/thread-local storage.

Catching Values Thrown by Lisp

This is similar to handling Lisp errors. The only difference is ErrorKind::Throw being used instead of ErrorKind::Signal.

Handling Rust errors in Lisp

In addition to standard errors, Rust module functions can signal Rust-specific errors, which can also be handled by condition-case:

  • rust-error: The message is Rust error. This covers all generic Rust-originated errors.
  • rust-wrong-type-user-ptr: The message is Wrong type user-ptr. This happens when Rust code is passed a user-ptr of a type it's not expecting. It is a sub-type of rust-error.
    
    # #![allow(unused_variables)]
    #fn main() {
    // May signal if `value` holds a different type of hash map,
    // or is a `user-ptr` defined in a non-Rust module.
    let r: &RefCell<HashMap<String, String>> = value.into_rust()?;
    #}

Panics

Unwinding from Rust into C is undefined behavior. emacs-module-rs prevents that by using catch_unwind at the Rust-to-C boundary to convert a panic into a Lisp's signal/throw of the appropriate type:

  • Normally the panic is converted into a Lisp's error signal of the type rust-panic. Note that it is not a sub-type of rust-error.
  • If the panic value is an ErrorKind, it is converted to the corresponding signal/throw, as if a Result was returned. This allows propagating Lisp's non-local exits through contexts where Result is not appropriate, e.g. callbacks whose types are dictated by 3rd-party libraries, such as tree-sitter.