• Option
• Result
• The ? operator propagates errors up the call stack concisely
• Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
• The compiler forces you to handle every error case — no silent failures
• Option
• Result
• The ? operator propagates errors up the call stack concisely
• Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
• The compiler forces you to handle every error case — no silent failures
use std::os::raw::c_int;
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PosixError {
NotFound = -2, // ENOENT
AccessDenied = -13, // EACCES
AlreadyExists = -17, // EEXIST
InvalidArg = -22, // EINVAL
Unknown = i32::MIN,
}
impl PosixError {
pub fn from_raw(code: c_int) -> Self {
match code {
-2 => Self::NotFound,
-13 => Self::AccessDenied,
-17 => Self::AlreadyExists,
-22 => Self::InvalidArg,
_ => Self::Unknown,
}
}
}
/// Wraps a C call that returns 0 on success, negative on error.
fn check(rc: c_int) -> Result<(), PosixError> {
if rc == 0 { Ok(()) } else { Err(PosixError::from_raw(rc)) }
}
/// Safe Rust API hiding the C error-code convention.
pub fn safe_open(path: &str) -> Result<i32, PosixError> {
let rc = unsafe { c_open(path.as_ptr(), path.len()) };
check(rc).map(|_| rc)
}
For `errno`-style errors (the C function sets the global `errno` on failure), use `std::io::Error::last_os_error()` and map it to your error type.
| Concept | OCaml | Rust |
|---|---|---|
| C error handling | `Unix.Unix_error` exception | `Result<T, E>` returned |
| errno capture | `Unix.errno` | `std::io::Error::last_os_error()` |
| Error codes | `Unix.error` variants | Custom `#[repr(i32)]` enum |
| Safe wrapper boundary | Module + exception | `fn safe_foo() -> Result<T, MyError>` |
| Zero = success | Checked by Unix module | Checked and converted in wrapper |
| Propagation | `try`/`with` | `?` operator |
// 715. C-style error code patterns in Rust FFI
//
// Demonstrates wrapping C integer-return-code conventions into
// idiomatic Result<T, E> at the FFI boundary.
use std::fmt;
use std::os::raw::{c_char, c_int};
// ── Error type ───────────────────────────────────────────────────────────────
/// Maps POSIX errno values to a typed Rust enum.
/// `#[repr(i32)]` ensures the discriminant fits a C int.
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PosixError {
NotFound = -2, // ENOENT
AccessDenied = -13, // EACCES
AlreadyExists = -17, // EEXIST
InvalidArg = -22, // EINVAL
Unknown = i32::MIN,
}
impl PosixError {
/// Convert a raw negative C return value to a typed error.
pub fn from_raw(code: c_int) -> Self {
match code {
-2 => Self::NotFound,
-13 => Self::AccessDenied,
-17 => Self::AlreadyExists,
-22 => Self::InvalidArg,
_ => Self::Unknown,
}
}
}
impl fmt::Display for PosixError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
Self::NotFound => "No such file or directory (ENOENT)",
Self::AccessDenied => "Permission denied (EACCES)",
Self::AlreadyExists => "File exists (EEXIST)",
Self::InvalidArg => "Invalid argument (EINVAL)",
Self::Unknown => "Unknown error",
};
f.write_str(msg)
}
}
impl std::error::Error for PosixError {}
// ── Mock C library ────────────────────────────────────────────────────────────
// In real code this block would be:
// extern "C" { fn open(path: *const c_char, flags: c_int) -> c_int; }
//
// We stub it here so the example compiles without a C toolchain.
mod mock_libc {
use std::os::raw::c_int;
/// Simulates a C function: returns >=0 on success, negative on error.
/// Real signature: `unsafe extern "C" fn open(*const c_char, c_int) -> c_int`
pub unsafe fn open(path_ptr: *const u8, _flags: c_int) -> c_int {
// SAFETY: caller guarantees path_ptr is a valid, null-terminated C string.
// We read only until we find a null byte, so no OOB access.
let mut len = 0usize;
while *path_ptr.add(len) != 0 {
len += 1;
}
let path = std::str::from_utf8(std::slice::from_raw_parts(path_ptr, len))
.unwrap_or("");
if path.contains("missing") {
-2 // ENOENT
} else if path.contains("secret") {
-13 // EACCES
} else {
3 // fake fd
}
}
pub unsafe fn close(_fd: c_int) -> c_int {
// SAFETY: caller guarantees fd is a valid open file descriptor.
0 // success
}
}
// ── Safe wrapper ──────────────────────────────────────────────────────────────
/// A safe file descriptor handle that closes on drop.
pub struct OwnedFd(c_int);
impl Drop for OwnedFd {
fn drop(&mut self) {
// SAFETY: self.0 was obtained from a successful open() call and
// has not been closed yet (we own it exclusively).
unsafe { mock_libc::close(self.0); }
}
}
impl OwnedFd {
pub fn raw(&self) -> c_int { self.0 }
}
/// Open a file, converting C error codes to `Result`.
pub fn open(path: &str) -> Result<OwnedFd, PosixError> {
// Build a null-terminated byte string on the stack.
let mut buf = [0u8; 256];
let bytes = path.as_bytes();
if bytes.len() >= buf.len() {
return Err(PosixError::InvalidArg);
}
buf[..bytes.len()].copy_from_slice(bytes);
// buf[bytes.len()] is already 0 (null terminator).
// SAFETY: `buf` is a valid null-terminated byte array living on the stack.
// We pass a pointer to it; the C function does not retain the pointer after
// it returns, so there are no lifetime issues.
let fd = unsafe { mock_libc::open(buf.as_ptr(), 0) };
if fd >= 0 {
Ok(OwnedFd(fd))
} else {
Err(PosixError::from_raw(fd))
}
}
// ── Convenience: check-and-return pattern ─────────────────────────────────────
/// Helper used throughout the codebase: turn any negative C int into an error.
#[inline]
pub fn check(rc: c_int) -> Result<c_int, PosixError> {
if rc >= 0 { Ok(rc) } else { Err(PosixError::from_raw(rc)) }
}
// ── main ──────────────────────────────────────────────────────────────────────
fn main() {
let paths = &["normal_file.txt", "missing_file.txt", "secret_data.bin"];
for path in paths {
match open(path) {
Ok(fd) => println!("Opened '{}' → fd={}", path, fd.raw()),
Err(e) => println!("Failed '{}' → {}", path, e),
}
}
// Demonstrate the `check()` helper with a raw C return value.
let raw: c_int = -22; // pretend a C call returned EINVAL
match check(raw) {
Ok(v) => println!("C call succeeded: {}", v),
Err(e) => println!("C call failed: {}", e),
}
}
// ── Tests ─────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn success_path() {
let fd = open("normal_file.txt").expect("should open");
assert!(fd.raw() >= 0);
}
#[test]
fn enoent() {
let err = open("missing_file.txt").unwrap_err();
assert_eq!(err, PosixError::NotFound);
}
#[test]
fn eacces() {
let err = open("secret_data.bin").unwrap_err();
assert_eq!(err, PosixError::AccessDenied);
}
#[test]
fn check_helper_positive() {
assert_eq!(check(0), Ok(0));
assert_eq!(check(42), Ok(42));
}
#[test]
fn check_helper_negative() {
assert_eq!(check(-2), Err(PosixError::NotFound));
}
#[test]
fn error_display() {
assert!(PosixError::AccessDenied.to_string().contains("EACCES"));
}
}
(* OCaml FFI: C-style error codes via the Unix module *)
(* Declare an external C function that returns an int error code.
By convention: 0 = success, negative = error. *)
external c_open : string -> int -> int = "caml_unix_open"
(* A typed error variant — mirrors what we'd see in errno.h *)
type posix_error =
| ENOENT (* No such file or directory *)
| EACCES (* Permission denied *)
| EEXIST (* File exists *)
| Unknown of int
let posix_error_of_int = function
| -2 -> ENOENT
| -13 -> EACCES
| -17 -> EEXIST
| n -> Unknown n
(* Wrap the C call: return Result-style using a variant *)
type ('a, 'e) result = Ok of 'a | Error of 'e
let safe_open path flags : (int, posix_error) result =
let fd = c_open path flags in
if fd >= 0 then Ok fd
else Error (posix_error_of_int fd)
(* Higher-level: compose over result *)
let with_file path flags f =
match safe_open path flags with
| Error e -> Error e
| Ok fd ->
let r = f fd in
(* close(fd) call elided for brevity *)
Ok r
(* Simulate the pattern without actual C call *)
let simulate_c_call succeed =
if succeed then 3 (* fake fd *) else -2 (* ENOENT *)
let () =
let r =
let raw = simulate_c_call false in
if raw >= 0 then Ok raw
else Error (posix_error_of_int raw)
in
match r with
| Ok fd -> Printf.printf "Opened fd=%d\n" fd
| Error ENOENT -> Printf.printf "File not found\n"
| Error EACCES -> Printf.printf "Permission denied\n"
| Error _ -> Printf.printf "Unknown error\n"