alef 0.81.0

Opinionated polyglot binding generator for Rust libraries
Documentation
thread_local! {
    static LAST_ERROR_CODE: RefCell<i32> = const { RefCell::new(0) };
    static LAST_ERROR_CONTEXT: RefCell<Option<CString>> = const { RefCell::new(None) };
    static LAST_RETURN_LENGTHS: RefCell<std::collections::BTreeMap<&'static str, usize>> =
        const { RefCell::new(std::collections::BTreeMap::new()) };
}

#[repr(i32)]
pub enum AlefFfiErrorCode {
    {{ builtin_prefix }}None = {{ no_error_code }},
    {{ builtin_prefix }}Conversion = {{ conversion_error_code }},
    {{ builtin_prefix }}Unknown = {{ unknown_error_code }},
    {{ builtin_prefix }}Panic = {{ panic_error_code }},
    {{ builtin_prefix }}InvalidHandle = {{ invalid_handle_error_code }},
{% for entry in taxonomy %}
    {{ entry.enum_variant }} = {{ entry.code }},
{% endfor %}
}

const ALEF_FFI_CONVERSION_ERROR: i32 = AlefFfiErrorCode::{{ builtin_prefix }}Conversion as i32;
const ALEF_FFI_UNKNOWN_ERROR: i32 = AlefFfiErrorCode::{{ builtin_prefix }}Unknown as i32;
const ALEF_FFI_PANIC_ERROR: i32 = AlefFfiErrorCode::{{ builtin_prefix }}Panic as i32;
const ALEF_INVALID_HANDLE_ERROR: i32 = AlefFfiErrorCode::{{ builtin_prefix }}InvalidHandle as i32;

fn alef_ffi_error_code({% if has_error_code_impls %}error{% else %}_error{% endif %}: &dyn std::any::Any) -> i32 {
{% for error_code_impl in error_code_impls %}
    if let Some(error) = error.downcast_ref::<{{ error_code_impl.error_path }}>() {
        #[allow(unreachable_patterns)]
        return match error {
{% for variant in error_code_impl.variants %}
            {{ variant.pattern }} => {{ variant.code_expression }},
{% endfor %}
            _ => ALEF_FFI_UNKNOWN_ERROR,
        };
    }
{% endfor %}
    ALEF_FFI_UNKNOWN_ERROR
}

fn set_last_error(code: i32, message: &str) {
    LAST_ERROR_CODE.with_borrow_mut(|c| *c = code);
    LAST_ERROR_CONTEXT.with_borrow_mut(|c| *c = CString::new(message).ok());
}

fn clear_last_error() {
    LAST_ERROR_CODE.with_borrow_mut(|c| *c = 0);
    LAST_ERROR_CONTEXT.with_borrow_mut(|c| *c = None);
}

fn set_panic_error() {
    set_last_error(ALEF_FFI_PANIC_ERROR, "Rust panic contained at FFI boundary");
}

fn catch_ffi_panic<T>(fallback: T, body: impl FnOnce() -> T) -> T {
    clear_last_error();
    catch_ffi_panic_preserving_error(fallback, body)
}

fn catch_ffi_panic_preserving_error<T>(fallback: T, body: impl FnOnce() -> T) -> T {
    match std::panic::catch_unwind(std::panic::AssertUnwindSafe(body)) {
        Ok(value) => value,
        Err(_) => {
            // A panic can unwind past code that already reported a more specific error via
            // set_last_error/set_handle_error (e.g. a conversion failure right before an
            // unrelated panic deeper in the same call). Unlike `catch_ffi_panic` (which always
            // clears first), this wrapper's whole contract is to leave that in place, so the
            // generic panic marker is stamped only when nothing more specific already is. ~keep
            if LAST_ERROR_CODE.with_borrow(|c| *c == 0) {
                let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(set_panic_error));
            }
            fallback
        }
    }
}

fn set_last_return_len(function: &'static str, len: usize) {
    LAST_RETURN_LENGTHS.with_borrow_mut(|lengths| {
        lengths.insert(function, len);
    });
}

fn last_return_len(function: &'static str) -> usize {
    LAST_RETURN_LENGTHS.with_borrow(|lengths| lengths.get(function).copied().unwrap_or(0))
}

/// Return the last error code (0 means no error).
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// This function does not allocate and returns no owned pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn {{ prefix }}_last_error_code() -> i32 {
    catch_ffi_panic_preserving_error(ALEF_FFI_PANIC_ERROR, || LAST_ERROR_CODE.with_borrow(|c| *c))
}

/// Return the last error message. The pointer is borrowed and valid until the next FFI call on this thread.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// The returned pointer is borrowed from thread-local storage and must NOT be freed.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn {{ prefix }}_last_error_context() -> *const c_char {
    catch_ffi_panic_preserving_error(std::ptr::null(), || LAST_ERROR_CONTEXT.with_borrow(|ctx| {
        ctx.as_ref().map_or(std::ptr::null(), |c| c.as_ptr())
    }))
}