Ensure C-ABI Compatibility for Strings Between Rust and C

2026-08-30 浏览 (1)


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 boundaries
  • ffi-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 for Uninitialized Memory

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

Handle Errors Properly in FFI

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