id: ffi-01 original_id: P.UNS.FFI.01 level: P impact: HIGH
Avoid Passing Strings Directly to C from Public Rust API
Summary
Use CString and CStr for string handling at FFI boundaries. Never pass Rust String or &str directly to C.
Rationale
- Rust strings are UTF-8, not null-terminated
- C strings require null terminator
- Rust strings may contain interior null bytes
- Memory layout differs between Rust String and C char*
Bad Example
extern "C" {
fn c_print(s: *const u8);
fn c_strlen(s: *const u8) -> usize;
}
// DON'T: Pass Rust string directly
fn bad_print(s: &str) {
unsafe {
c_print(s.as_ptr()); // Not null-terminated!
}
}
// DON'T: Assume length matches
fn bad_strlen(s: &str) -> usize {
unsafe {
c_strlen(s.as_ptr()) // May read past buffer
}
}
// DON'T: Use String in FFI signatures
extern "C" fn bad_callback(s: String) { // Wrong!
println!("{}", s);
}
Good Example
use std::ffi::{CString, CStr};
use std::os::raw::c_char;
extern "C" {
fn c_print(s: *const c_char);
fn c_strlen(s: *const c_char) -> usize;
fn c_get_string() -> *const c_char;
}
// DO: Convert to CString for passing to C
fn good_print(s: &str) -> Result<(), std::ffi::NulError> {
let c_string = CString::new(s)?; // Adds null terminator, checks for interior nulls
unsafe {
c_print(c_string.as_ptr());
}
Ok(())
}
// DO: Use CStr for receiving C strings
fn good_receive() -> String {
unsafe {
let ptr = c_get_string();
let c_str = CStr::from_ptr(ptr);
c_str.to_string_lossy().into_owned()
}
}
// DO: Handle interior null bytes
fn handle_nulls(s: &str) {
match CString::new(s) {
Ok(c_string) => unsafe { c_print(c_string.as_ptr()) },
Err(e) => {
// String contains interior null at position e.nul_position()
eprintln!("String contains null byte at {}", e.nul_position());
}
}
}
// DO: Use proper types in callbacks
extern "C" fn good_callback(s: *const c_char) {
if !s.is_null() {
let c_str = unsafe { CStr::from_ptr(s) };
if let Ok(rust_str) = c_str.to_str() {
println!("{}", rust_str);
}
}
}
String Type Comparison
| Type | Null-terminated | Encoding | Use |
|---|---|---|---|
String | No | UTF-8 | Rust owned |
&str | No | UTF-8 | Rust borrowed |
CString | Yes | Byte | Rust-to-C owned |
&CStr | Yes | Byte | Rust-to-C borrowed |
*const c_char | Yes | Byte | FFI pointer |
OsString | Platform | Platform | Paths, env |
Checklist
- Am I passing Rust strings to C? → Use CString
- Am I receiving C strings? → Use CStr
- Does my string contain null bytes? → Handle NulError
- Am I checking for null pointers from C?
Related Rules
ffi-02: Read documentation for std::ffi typesffi-06: Ensure C-ABI string compatibility
你可能感兴趣的文章
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