id: ffi-06 original_id: P.UNS.FFI.06 level: P impact: HIGH
Ensure C-ABI Compatibility for Strings Between Rust and C
Summary
When passing strings across FFI, ensure both sides agree on encoding, null-termination, and memory ownership.
Rationale
- Rust strings are UTF-8, C strings are byte arrays
- C expects null termination, Rust strings don't have it
- Memory ownership must be explicit to avoid leaks/double-frees
String Passing Patterns
Rust to C (Caller Allocates)
use std::ffi::CString;
use std::os::raw::c_char;
extern "C" {
fn c_process_string(s: *const c_char);
}
fn rust_to_c(s: &str) -> Result<(), std::ffi::NulError> {
let c_string = CString::new(s)?;
// c_string lives until end of scope
unsafe {
c_process_string(c_string.as_ptr());
}
// c_string dropped here, memory freed
Ok(())
}
C to Rust (C Allocates, Rust Borrows)
use std::ffi::CStr;
use std::os::raw::c_char;
extern "C" {
fn c_get_string() -> *const c_char;
}
fn c_to_rust() -> Option<String> {
let ptr = unsafe { c_get_string() };
if ptr.is_null() {
return None;
}
// Borrow from C, don't take ownership
let c_str = unsafe { CStr::from_ptr(ptr) };
Some(c_str.to_string_lossy().into_owned())
}
C to Rust (Ownership Transfer)
extern "C" {
fn c_create_string() -> *mut c_char;
fn c_free_string(s: *mut c_char);
}
struct CAllocatedString {
ptr: *mut c_char,
}
impl CAllocatedString {
fn new() -> Option<Self> {
let ptr = unsafe { c_create_string() };
if ptr.is_null() {
None
} else {
Some(Self { ptr })
}
}
fn as_str(&self) -> &str {
let c_str = unsafe { CStr::from_ptr(self.ptr) };
c_str.to_str().unwrap_or("")
}
}
impl Drop for CAllocatedString {
fn drop(&mut self) {
unsafe { c_free_string(self.ptr); }
}
}
Rust to C (Ownership Transfer)
extern "C" {
fn c_take_ownership(s: *mut c_char); // C will free
}
fn give_to_c(s: &str) -> Result<(), std::ffi::NulError> {
let c_string = CString::new(s)?;
let ptr = c_string.into_raw(); // Don't drop CString
unsafe {
c_take_ownership(ptr);
// C now owns this memory
// To free it back in Rust: let _ = CString::from_raw(ptr);
}
Ok(())
}
Encoding Considerations
// UTF-8 to platform encoding
use std::ffi::OsString;
use std::os::unix::ffi::OsStrExt;
fn to_platform_string(s: &str) -> CString {
// On Unix, UTF-8 usually works
CString::new(s).unwrap()
}
#[cfg(windows)]
fn to_wide_string(s: &str) -> Vec<u16> {
use std::os::windows::ffi::OsStrExt;
std::ffi::OsStr::new(s)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
Checklist
- Is the string null-terminated when passed to C?
- Who allocates the memory? Who frees it?
- Is the encoding (UTF-8, ASCII, platform) documented?
- Am I handling conversion errors (interior nulls, invalid UTF-8)?
Related Rules
ffi-01: Use CString/CStr at FFI boundariesffi-02: Read std::ffi documentation
你可能感兴趣的文章
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
Do Not Dereference Pointers Cast to Misaligned Types
Use Dedicated Opaque Type Pointers Instead of c_void for C Opaque Types