Course contentsShow
← Rust
Lesson 902 of 1,9107. Error Handling PatternsFree lesson

Error Handling Best Practices for FFI

Bridge C-ABI return codes to Rust-idiomatic result types consistently.

In this lesson, you will learn how to bridge C-ABI return codes to Rust-idiomatic Result types to ensure your foreign function interfaces are both robust and predictable.

When interacting with C, you rarely have the luxury of Rust’s Result<T, E>. Instead, C functions typically signal success or failure via integer return codes—often 0 for success and non-zero values for specific error domains. To keep your Rust code idiomatic, you must convert these low-level integers into typed errors as soon as they cross the FFI boundary.

The Translation Layer

Do not allow raw status codes to propagate through your application logic. Create a translation function that encapsulates the FFI call and maps the status codes to your project's custom error enum.

Consider this pattern:

#[repr(i32)]
enum CStatus {
    Success = 0,
    InvalidInput = 1,
    ConnectionLost = 2,
}

fn call_ffi_operation() -> Result<(), MyError> {
    let status = unsafe { ffi_c_function() };
    
    match status {
        0 => Ok(()),
        1 => Err(MyError::InvalidInput),
        2 => Err(MyError::NetworkError),
        _ => Err(MyError::Unknown(status)),
    }
}

Why this approach matters

By using a match statement immediately after the unsafe block, you achieve several goals:

  1. Domain Isolation: Your internal business logic only deals with Rust-native Result types, shielding the rest of your codebase from the C-ABI's quirks.
  2. Exhaustive Mapping: Rust’s compiler forces you to handle unexpected status codes (the _ arm), preventing "silent failures" where a new C error code is added but ignored.
  3. Safety Boundaries: This boundary acts as a "sanity check" layer where you can perform additional validation—such as checking if the FFI function modified memory it shouldn't have—before converting the result into a type-safe format.

This bridge effectively turns the "wild west" of C return codes into the type-safe, ergonomic error handling that Rust developers expect. Treat every FFI return value as untrusted input that must be validated into your internal error domain immediately.

Key Takeaway: Always wrap FFI calls in a dedicated layer that transforms integer status codes into domain-specific Result types before they reach your business logic.