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
| Pattern | Usage |
|---|---|
| Return code | Simple success/failure |
| Return code + out param | Return value on success |
| errno | POSIX-style APIs |
| Error message function | Detailed error info |
| Last-error thread-local | Windows-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 boundarysafety-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
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