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 isRust error
. This covers all generic Rust-originated errors.rust-wrong-type-user-ptr
: The message isWrong type user-ptr
. This happens when Rust code is passed auser-ptr
of a type it's not expecting. It is a sub-type ofrust-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 ofrust-error
. - If the panic value is an
ErrorKind
, it is converted to the corresponding signal/throw, as if aResult
was returned. This allows propagating Lisp's non-local exits through contexts whereResult
is not appropriate, e.g. callbacks whose types are dictated by 3rd-party libraries, such astree-sitter
.