google/safer_cffi

Rust

5

20 commits

updated Sep 15, 2026

See the code

README

Building Blocks for Safer C FFI

safer_cffi provides Rust primitives for replacing C libraries with memory-safe Rust implementations while maintaining full ABI compatibility with the original C headers.

  • Pointer Trackers — Manage the lifecycle of Rust objects handed to C as opaque handles or raw pointers, preventing use-after-free and double-borrow bugs.

  • Struct Field Helpers — Safe wrappers for common C struct field patterns (*mut T/c_int array pairs and const char* strings).

Tip: Take a look at examples/ for patterns and common use cases.

Pointer Trackers

Trackers manage the lifecycle of Rust objects that are handed to C code as opaque handles or pointers. They ensure objects are only accessed when valid and prevent concurrent mutable access.

Both trackers implement the Tracker<T> trait, which provides:

  • register(Box<T>) → Key — stores the object, returns a key for C.
  • borrow_mut(key) → Tracked<T> — exclusive access via a RAII guard.
  • reclaim(key) → Box<T> — takes back ownership, removing from tracker.

Uses generational Handle<T> IDs. Prevents use-after-free via generation checks and detects double-borrows.

static TRACKER: safer_cffi::OpaqueTracker<MyObj> = safer_cffi::OpaqueTracker::new();

#[unsafe(no_mangle)]
pub extern "C" fn create() -> Handle<MyObj> {
    TRACKER.register(Box::new(MyObj::default())).unwrap_or_else(|_| Handle::null())
}

#[unsafe(no_mangle)]
pub extern "C" fn destroy(h: Handle<MyObj>) {
    let _ = TRACKER.reclaim(h);
}

See examples/opaque_tracker/ for a full example.

RawTracker<T>

Uses raw memory addresses (*mut T) as keys. Use only when C code needs the actual pointer value (e.g. for direct field access). Caveats:

  • Cannot be used if C creates objects — all objects must originate from Rust.
  • Does not prevent ABA problems: a freed and re-allocated address silently resolves to the new object.

See examples/raw_tracker/ for a full example.

Struct Field Helpers

C Slices — CBufPtr<T> and OwnedCBufPtr<T>

Many C structs contain (*mut T, L) pairs representing dynamically-sized arrays (where L is an integer length type such as c_int or usize). CBufPtr and OwnedCBufPtr can be used in place of *mut T and provide safe handles for access and manipulation.

  • CBufPtr<T, A = LibcAlloc>: Unowned buffer pointer wrapper (#[repr(transparent)] around *mut T). Does not deallocate on drop.

    • with_len(len) → &[T] — shared slice view for any len: L where L: CBufLen.
    • with_len_mut(len) → &mut [T] — mutable slice view for any len: L where L: CBufLen and A = LibcAlloc.
    • as_vec_mut(&mut len) → CVecRefMut<'_, T, L, A> — mutable vector handle for any L: CBufLen (when A: Default).
    • as_vec_mut_in(&mut len, alloc) → CVecRefMut<'_, T, L, A> — mutable vector handle with custom allocator instance.
    • as_vec_mut_with_cap(&mut len, &mut cap) → CVecRefMut<'_, T, L, A, C> — capacity-tracking mutable vector handle. The extra cap: C field lets push_back reuse spare capacity and grow geometrically, saving reallocations.
    • as_vec_mut_with_cap_in(&mut len, &mut cap, alloc) → CVecRefMut<'_, T, L, A, C> — capacity-tracking handle with a custom allocator instance.
    • clone_and_leak(&[T]) → CBufPtr<T> — create a new CBufPtr by cloning an existing slice using LibcAlloc.
    • clone_and_leak_in(&[T], alloc) → CBufPtr<T, A> — create a new CBufPtr by cloning an existing slice using a custom allocator.
  • OwnedCBufPtr<T: Copy, A: DropByPtrAllocator = LibcAlloc>: RAII-owning buffer pointer wrapper (#[repr(transparent)] around *mut T). Automatically deallocates the underlying heap memory when dropped using A::deallocate_by_ptr (e.g. libc::free). Because T: Copy (which implies !Drop), elements do not require individual destruction, allowing the buffer to be freed without tracking length at drop time. Dereferences (Deref/DerefMut) to CBufPtr<T, A>, providing access to all CBufPtr methods.

    • null() → OwnedCBufPtr<T, A> — create a null owned buffer pointer.
    • from_raw(raw) → OwnedCBufPtr<T, A> (unsafe) — construct from a raw pointer, transferring ownership.
    • into_c_buf_ptr(self) → CBufPtr<T, A> — extract inner CBufPtr without deallocating.
    • into_raw(self) → *mut T — extract raw pointer without deallocating.
  • CVecRefMut<'a, T, L, A = LibcAlloc, C = L>: A borrowed mutable "vec-like" struct. Implements DerefMut to &mut [T]. When constructed via as_vec_mut_with_cap[_in], it also borrows a capacity field of type C. Additional methods:

    • push_back(T) / try_push_back(T) — append an element. When capacity is tracked, appends into spare capacity without reallocating and grows geometrically once full; otherwise reallocates by one slot per push.
    • capacity() → Option<usize> — the tracked capacity, if any.
    • clear() — drop all elements, free the (full-capacity) allocation via the allocator, and reset to null/0.
    • swap(&mut CVecRefMut) — swap two handles (pointer, len, and capacity) using the same allocator.

Usage example:

#[repr(C)]
struct MyStruct {
    // Safety invariant: the length of this array is `item_len`.
    items: OwnedCBufPtr<Item>,
    item_len: c_int,
}

impl MyStruct {
    fn items(&self) -> &[Item] {
        // SAFETY: the length of `items` is `item_len`.
        unsafe { self.items.with_len(self.item_len) }
    }
    fn items_mut(&mut self) -> &mut [Item] {
        // SAFETY: the length of `items` is `item_len`.
        unsafe { self.items.with_len_mut(self.item_len) }
    }
    fn items_vec_mut(&mut self) -> CVecRefMut<'_, Item, c_int> {
        // SAFETY: the length of `items` is `item_len`.
        unsafe { self.items.as_vec_mut(&mut self.item_len) }
    }
}

See examples/c_buf_ptr/ for a full example.

C Strings — CStrRef<'a>

A #[repr(transparent)] wrapper around NonNull<c_char> that can be used as Option<CStrRef<'_>> in FFI signatures where a nullable C string is expected. Unlike core::ffi::CStr, it guarantees a thin pointer layout and ABI compatibility with a C const char*.

Usage example:

use safer_cffi::CStrRef;

#[unsafe(no_mangle)]
pub extern "C" fn print_string(s: Option<CStrRef<'_>>) {
    if let Some(c_str) = s {
        // CStrRef can be safely converted to a &CStr
        println!("Received: {}", c_str.to_c_str().to_string_lossy());
    }
}

This is not an officially supported Google product. This project is not eligible for the Google Open Source Software Vulnerability Rewards Program.

google/safer_cffi

Rust

5

20 commits

updated Sep 15, 2026

See the code

README

Building Blocks for Safer C FFI

safer_cffi provides Rust primitives for replacing C libraries with memory-safe Rust implementations while maintaining full ABI compatibility with the original C headers.

  • Pointer Trackers — Manage the lifecycle of Rust objects handed to C as opaque handles or raw pointers, preventing use-after-free and double-borrow bugs.

  • Struct Field Helpers — Safe wrappers for common C struct field patterns (*mut T/c_int array pairs and const char* strings).

Tip: Take a look at examples/ for patterns and common use cases.

Pointer Trackers

Trackers manage the lifecycle of Rust objects that are handed to C code as opaque handles or pointers. They ensure objects are only accessed when valid and prevent concurrent mutable access.

Both trackers implement the Tracker<T> trait, which provides:

  • register(Box<T>) → Key — stores the object, returns a key for C.
  • borrow_mut(key) → Tracked<T> — exclusive access via a RAII guard.
  • reclaim(key) → Box<T> — takes back ownership, removing from tracker.

Uses generational Handle<T> IDs. Prevents use-after-free via generation checks and detects double-borrows.

static TRACKER: safer_cffi::OpaqueTracker<MyObj> = safer_cffi::OpaqueTracker::new();

#[unsafe(no_mangle)]
pub extern "C" fn create() -> Handle<MyObj> {
    TRACKER.register(Box::new(MyObj::default())).unwrap_or_else(|_| Handle::null())
}

#[unsafe(no_mangle)]
pub extern "C" fn destroy(h: Handle<MyObj>) {
    let _ = TRACKER.reclaim(h);
}

See examples/opaque_tracker/ for a full example.

RawTracker<T>

Uses raw memory addresses (*mut T) as keys. Use only when C code needs the actual pointer value (e.g. for direct field access). Caveats:

  • Cannot be used if C creates objects — all objects must originate from Rust.
  • Does not prevent ABA problems: a freed and re-allocated address silently resolves to the new object.

See examples/raw_tracker/ for a full example.

Struct Field Helpers

C Slices — CBufPtr<T> and OwnedCBufPtr<T>

Many C structs contain (*mut T, L) pairs representing dynamically-sized arrays (where L is an integer length type such as c_int or usize). CBufPtr and OwnedCBufPtr can be used in place of *mut T and provide safe handles for access and manipulation.

  • CBufPtr<T, A = LibcAlloc>: Unowned buffer pointer wrapper (#[repr(transparent)] around *mut T). Does not deallocate on drop.

    • with_len(len) → &[T] — shared slice view for any len: L where L: CBufLen.
    • with_len_mut(len) → &mut [T] — mutable slice view for any len: L where L: CBufLen and A = LibcAlloc.
    • as_vec_mut(&mut len) → CVecRefMut<'_, T, L, A> — mutable vector handle for any L: CBufLen (when A: Default).
    • as_vec_mut_in(&mut len, alloc) → CVecRefMut<'_, T, L, A> — mutable vector handle with custom allocator instance.
    • as_vec_mut_with_cap(&mut len, &mut cap) → CVecRefMut<'_, T, L, A, C> — capacity-tracking mutable vector handle. The extra cap: C field lets push_back reuse spare capacity and grow geometrically, saving reallocations.
    • as_vec_mut_with_cap_in(&mut len, &mut cap, alloc) → CVecRefMut<'_, T, L, A, C> — capacity-tracking handle with a custom allocator instance.
    • clone_and_leak(&[T]) → CBufPtr<T> — create a new CBufPtr by cloning an existing slice using LibcAlloc.
    • clone_and_leak_in(&[T], alloc) → CBufPtr<T, A> — create a new CBufPtr by cloning an existing slice using a custom allocator.
  • OwnedCBufPtr<T: Copy, A: DropByPtrAllocator = LibcAlloc>: RAII-owning buffer pointer wrapper (#[repr(transparent)] around *mut T). Automatically deallocates the underlying heap memory when dropped using A::deallocate_by_ptr (e.g. libc::free). Because T: Copy (which implies !Drop), elements do not require individual destruction, allowing the buffer to be freed without tracking length at drop time. Dereferences (Deref/DerefMut) to CBufPtr<T, A>, providing access to all CBufPtr methods.

    • null() → OwnedCBufPtr<T, A> — create a null owned buffer pointer.
    • from_raw(raw) → OwnedCBufPtr<T, A> (unsafe) — construct from a raw pointer, transferring ownership.
    • into_c_buf_ptr(self) → CBufPtr<T, A> — extract inner CBufPtr without deallocating.
    • into_raw(self) → *mut T — extract raw pointer without deallocating.
  • CVecRefMut<'a, T, L, A = LibcAlloc, C = L>: A borrowed mutable "vec-like" struct. Implements DerefMut to &mut [T]. When constructed via as_vec_mut_with_cap[_in], it also borrows a capacity field of type C. Additional methods:

    • push_back(T) / try_push_back(T) — append an element. When capacity is tracked, appends into spare capacity without reallocating and grows geometrically once full; otherwise reallocates by one slot per push.
    • capacity() → Option<usize> — the tracked capacity, if any.
    • clear() — drop all elements, free the (full-capacity) allocation via the allocator, and reset to null/0.
    • swap(&mut CVecRefMut) — swap two handles (pointer, len, and capacity) using the same allocator.

Usage example:

#[repr(C)]
struct MyStruct {
    // Safety invariant: the length of this array is `item_len`.
    items: OwnedCBufPtr<Item>,
    item_len: c_int,
}

impl MyStruct {
    fn items(&self) -> &[Item] {
        // SAFETY: the length of `items` is `item_len`.
        unsafe { self.items.with_len(self.item_len) }
    }
    fn items_mut(&mut self) -> &mut [Item] {
        // SAFETY: the length of `items` is `item_len`.
        unsafe { self.items.with_len_mut(self.item_len) }
    }
    fn items_vec_mut(&mut self) -> CVecRefMut<'_, Item, c_int> {
        // SAFETY: the length of `items` is `item_len`.
        unsafe { self.items.as_vec_mut(&mut self.item_len) }
    }
}

See examples/c_buf_ptr/ for a full example.

C Strings — CStrRef<'a>

A #[repr(transparent)] wrapper around NonNull<c_char> that can be used as Option<CStrRef<'_>> in FFI signatures where a nullable C string is expected. Unlike core::ffi::CStr, it guarantees a thin pointer layout and ABI compatibility with a C const char*.

Usage example:

use safer_cffi::CStrRef;

#[unsafe(no_mangle)]
pub extern "C" fn print_string(s: Option<CStrRef<'_>>) {
    if let Some(c_str) = s {
        // CStrRef can be safely converted to a &CStr
        println!("Received: {}", c_str.to_c_str().to_string_lossy());
    }
}

This is not an officially supported Google product. This project is not eligible for the Google Open Source Software Vulnerability Rewards Program.

Languages

Rust

100.0%