Handle Errors Properly in FFI

2026-08-30 浏览 (1)


id: ffi-08 original_id: P.UNS.FFI.08 level: P impact: HIGH

Handle Errors Properly in FFI

Summary

FFI functions must use C-compatible error handling (return codes, errno, out parameters). Rust's Result/Option don't cross FFI boundaries.

Rationale

  • C doesn't have Result or Option
  • Exceptions don't exist in C
  • Must use patterns C code understands

Bad Example

// DON'T: Return Result across FFI
#[no_mangle]
pub extern "C" fn bad_open(path: *const c_char) -> Result<Handle, Error> {
    // Result is not C-compatible!
    unimplemented!()
}

// DON'T: Return Option across FFI
#[no_mangle]
pub extern "C" fn bad_find(id: i32) -> Option<*mut Data> {
    // Option<*mut T> might work but is confusing
    unimplemented!()
}

Good Example

use std::os::raw::{c_char, c_int};

// Error codes
const SUCCESS: c_int = 0;
const ERR_NULL_PTR: c_int = 1;
const ERR_INVALID_PATH: c_int = 2;
const ERR_FILE_NOT_FOUND: c_int = 3;
const ERR_PERMISSION: c_int = 4;
const ERR_UNKNOWN: c_int = -1;

// DO: Return error code, output via pointer
#[no_mangle]
pub extern "C" fn open_file(
    path: *const c_char,
    out_handle: *mut *mut Handle
) -> c_int {
    if path.is_null() || out_handle.is_null() {
        return ERR_NULL_PTR;
    }

    let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
        Ok(s) => s,
        Err(_) => return ERR_INVALID_PATH,
    };

    match File::open(path_str) {
        Ok(file) => {
            let handle = Box::into_raw(Box::new(Handle { file }));
            unsafe { *out_handle = handle; }
            SUCCESS
        }
        Err(e) => {
            match e.kind() {
                std::io::ErrorKind::NotFound => ERR_FILE_NOT_FOUND,
                std::io::ErrorKind::PermissionDenied => ERR_PERMISSION,
                _ => ERR_UNKNOWN,
            }
        }
    }
}

// DO: Use errno for POSIX-style APIs
#[cfg(unix)]
#[no_mangle]
pub extern "C" fn posix_style_read(
    fd: c_int,
    buf: *mut u8,
    count: usize
) -> isize {
    if buf.is_null() {
        unsafe { *libc::__errno_location() = libc::EINVAL; }
        return -1;
    }

    // ... do read ...
    // On error:
    // unsafe { *libc::__errno_location() = error_code; }
    // return -1;

    count as isize
}

// DO: Provide error message function
thread_local! {
    static LAST_ERROR: std::cell::RefCell<Option<String>> = std::cell::RefCell::new(None);
}

#[no_mangle]
pub extern "C" fn get_error_message(buf: *mut c_char, len: usize) -> c_int {
    LAST_ERROR.with(|e| {
        if let Some(msg) = e.borrow().as_ref() {
            let bytes = msg.as_bytes();
            let copy_len = std::cmp::min(bytes.len(), len.saturating_sub(1));
            unsafe {
                std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, copy_len);
                *buf.add(copy_len) = 0;
            }
            SUCCESS
        } else {
            ERR_UNKNOWN
        }
    })
}

Error Handling Patterns

PatternUsage
Return codeSimple success/failure
Return code + out paramReturn value on success
errnoPOSIX-style APIs
Error message functionDetailed error info
Last-error thread-localWindows-style APIs

Checklist

  • Am I returning C-compatible error indicators?
  • Are output parameters used for return values?
  • Is there a way to get detailed error info?
  • Am I documenting all possible error codes?

Related Rules

  • ffi-04: Handle panics at FFI boundary
  • safety-10: Document safety requirements

你可能感兴趣的文章

Do Not Expose Raw Pointers in Public APIs

Unsafe Code Authors Must Verify Safety Invariants

Ensure Consistent Data Layout for Custom Types

Add Safety Section in Docs for Public Unsafe Functions

Do Not Use Union Variants Across Different Lifetimes

Use MaybeUninit for Uninitialized Memory

Ensure C-ABI Compatibility for Strings Between Rust and C

Do Not Dereference Pointers Cast to Misaligned Types

Use Dedicated Opaque Type Pointers Instead of c_void for C Opaque Types

Choose Appropriate Data Layout for Struct/Tuple/Enum

  • 所属分类: AI
  • 本文标签: rust
  • 版权声明: 本文链接 https://seaxiang.com/blog/nzOdidFM