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.
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.OpaqueTracker<T> (recommended)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:
See examples/raw_tracker/ for a full example.
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.
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.
Rust
100.0%
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.
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.OpaqueTracker<T> (recommended)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:
See examples/raw_tracker/ for a full example.
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.
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.
Rust
100.0%