Crate relay_ffi

source ·
Expand description

Utilities for error handling in FFI bindings.

This crate facilitates an errno-like error handling pattern: On success, the result of a function call is returned. On error, a thread-local marker is set that allows to retrieve the error, message, and a backtrace if available.

Catch Errors and Panics

The catch_unwind attribute annotates functions that can internally throw errors. It allows the use of the questionmark operator ? in a function that does not return Result. The error is then available using with_last_error:

use relay_ffi::catch_unwind;

#[catch_unwind]
unsafe fn parse_number() -> i32 {
    // use the questionmark operator for errors:
    let number: i32 = "42".parse()?;

    // return the value directly, not `Ok`:
    number * 2
}

Safety

Since function calls always need to return a value, this crate has to return std::mem::zeroed() as a placeholder in case of an error. This is unsafe for reference types and function pointers. Because of this, functions must be marked unsafe.

In most cases, FFI functions should return either repr(C) structs or pointers, in which case this is safe in principle. The author of the API is responsible for defining the contract, however, and document the behavior of custom structures in case of an error.

Examples

Annotate FFI functions with catch_unwind to capture errors. The error can be inspected via with_last_error:

use relay_ffi::{catch_unwind, with_last_error};

#[catch_unwind]
unsafe fn parse_number() -> i32 {
    "42".parse()?
}

let parsed = unsafe { parse_number() };
match with_last_error(|e| e.to_string()) {
    Some(error) => println!("errored with: {error}"),
    None => println!("result: {parsed}"),
}

To capture panics, register the panic hook early during library initialization:

use relay_ffi::{catch_unwind, with_last_error};

relay_ffi::set_panic_hook();

#[catch_unwind]
unsafe fn fail() {
    panic!("expected panic");
}

unsafe { fail() };

if let Some(description) = with_last_error(|e| e.to_string()) {
    println!("{description}");
}

Creating C-APIs

This is an example for exposing an API to C:

use std::ffi::CString;
use std::os::raw::c_char;

#[no_mangle]
pub unsafe extern "C" fn init_ffi() {
    relay_ffi::set_panic_hook();
}

#[no_mangle]
pub unsafe extern "C" fn last_strerror() -> *mut c_char {
    let ptr_opt = relay_ffi::with_last_error(|err| {
        CString::new(err.to_string())
            .unwrap_or_default()
            .into_raw()
    });

    ptr_opt.unwrap_or(std::ptr::null_mut())
}

Structs

  • An error representing a panic carrying the message as payload.

Functions

  • Registers a hook for capturing panics with backtraces.
  • Takes the last error, leaving None in its place.
  • Acquires a reference to the last error and passes it to the callback, if any.

Attribute Macros

  • Captures errors and panics in a thread-local on unsafe functions.