In this lesson, you will learn how to bridge the gap between Rust’s expressive Result<T, E> pattern and the rigid, integer-based error reporting common in C-style ABIs.
The Semantic Mismatch
Rust handles errors using the Result enum, which carries both data and context. However, foreign code (like C) typically communicates success or failure through integral return codes (e.g., 0 for success, -1 for error) or by modifying a pointer to an error struct. Because Rust’s Result is a language-level construct, it cannot cross the FFI boundary directly.
The Strategy: Error Marshalling
To propagate errors safely, you must convert your internal Rust errors into a stable, ABI-compliant format before returning to the caller. This involves two steps: conversion and out-parameter pointer passing.
Consider this approach:
#[repr(C)]
pub struct ErrorInfo {
pub code: i32,
pub message: *const libc::c_char,
}
#[no_mangle]
pub unsafe extern "C" fn perform_operation(out_err: *mut ErrorInfo) -> i32 {
match do_work() {
Ok(_) => 0,
Err(e) => {
if !out_err.is_null() {
// Marshalling error into C-compatible struct
*out_err = ErrorInfo {
code: 500,
message: std::ffi::CString::new(e.to_string()).unwrap().into_raw(),
};
}
-1
}
}
}
Critical Considerations
- Memory Ownership: When you allocate a string via
into_raw(), you are bypassing Rust’s RAII. You must provide a secondary FFI function (e.g.,free_error_info) that the foreign caller is responsible for invoking to prevent memory leaks. - Panic Boundaries: An
extern "C"function must never allow a Rust panic to unwind across the ABI boundary; this results in undefined behavior. Always wrap your internal logic instd::panic::catch_unwindand map any caught panics to a specific "Internal Panic" error code. - Stability: Ensure your error structs use
#[repr(C)]to guarantee layout compatibility. If you plan to expand the error struct, consider adding aversionfield or padding bytes to maintain forward compatibility.
Key Takeaway: Error propagation across FFI requires manual marshalling of Rust’s semantic types into stable C-structs, coupled with a strict contract for memory ownership and panic suppression.