Course contentsShow
← Rust
Lesson 1618 of 1,91014. System Integration and EcosystemFree lesson

Error Handling across Languages

You will propagate error states from C return codes into the Result type pattern.

In this lesson, you will learn how to bridge the semantic gap between C-style integer return codes and Rust’s expressive Result<T, E> pattern when performing FFI calls.

The Semantic Gap

C functions typically communicate status via return codes—usually 0 for success and a non-zero integer for various failure states. In Rust, this is idiomatic noise. To integrate this into a safe ecosystem, you must encapsulate the raw integer check within a conversion layer that maps domain-specific error codes into a robust Rust enum.

Translating Return Codes to Results

When calling an external C function, the boundary is inherently unsafe. Your goal is to keep the unsafe block as thin as possible, immediately wrapping the return value in a Result.

Consider a hypothetical C function int disk_write(const char* data) where -1 represents a permission error and -2 represents a hardware failure. You shouldn’t expose these integers to the rest of your application:

#[repr(i32)]
enum DiskError {
    PermissionDenied = -1,
    HardwareFailure = -2,
}

fn write_data(data: &str) -> Result<(), DiskError> {
    let result = unsafe { disk_write(data.as_ptr() as *const i8) };
    
    match result {
        0 => Ok(()),
        -1 => Err(DiskError::PermissionDenied),
        -2 => Err(DiskError::HardwareFailure),
        _ => panic!("Unknown error code: {}", result),
    }
}

The Pattern of Translation

By mapping these codes, you gain several benefits:

  1. Exhaustiveness: Rust’s compiler forces you to handle every possible error state returned by the C API.
  2. Contextual Typing: You transform a "magic number" into an enum that can implement std::error::Error, allowing it to integrate seamlessly with the ? operator in your higher-level logic.
  3. Safety Isolation: The translation layer acts as a "firewall," ensuring that the C language's permissive error reporting cannot leak into your own application's business logic.

When the C side uses -1 to mean "everything went wrong," it is your responsibility as a systems integrator to translate that ambiguity into a strongly-typed, context-aware error that adheres to Rust's safety guarantees.

Key Takeaway: Always intercept raw FFI return codes at the immediate boundary and transform them into typed Result enums to maintain memory and logic safety.