In this lesson, you will learn how to wrap integer-based error codes from C libraries into idiomatic Rust Result types.
When interacting with C APIs, functions typically communicate success or failure using integer return codes—often returning 0 for success and a negative value or a specific constant for errors. Because these raw codes provide no type safety, your goal is to "lift" them into Rust's Result type as soon as you cross the boundary from unsafe to safe code.
Mapping the Error
Consider a hypothetical C function int raw_connect(const char* host). It returns 0 on success and -1 if the host is unreachable. In Rust, we want a function that returns Result<(), ConnectionError>.
You should perform this conversion inside your safe wrapper. This prevents the "leakage" of integer error codes into the rest of your application:
#[derive(Debug)]
pub enum ConnectionError {
Unreachable,
Unknown(i32),
}
pub fn connect(host: &str) -> Result<(), ConnectionError> {
let c_host = std::ffi::CString::new(host).unwrap();
// The unsafe block is isolated to the foreign call
let code = unsafe { raw_connect(c_host.as_ptr()) };
match code {
0 => Ok(()),
-1 => Err(ConnectionError::Unreachable),
other => Err(ConnectionError::Unknown(other)),
}
}
Why This Matters
By handling the conversion immediately:
- Encapsulation: The rest of your crate works with a descriptive
enumrather than checking magic numbers. - Safety: You ensure that the C library's internal state is validated before your code proceeds. If the C function fails, the
Resultforces the caller to acknowledge the error via the?operator or amatchstatement. - Refactoring: If the underlying library changes its error schema (e.g., adding new codes), you only need to update the conversion logic in one place, keeping your higher-level logic completely decoupled from the C implementation.
Always treat C error codes as "poisoned" values that must be sanitized into meaningful Rust types before they touch your business logic.
Key Takeaway: Always transform raw C integer error codes into descriptive Rust Result enums at the immediate boundary of your unsafe code to maintain type safety and API ergonomics.