Rust FFI Safe Wrappers
Use this skill to wrap foreign interfaces in Rust APIs that make ownership,
lifetimes, errors, and cleanup explicit. FFI code should be unsafe at the raw
boundary and safe for normal Rust callers.
Core Workflow
- Separate raw declarations from safe wrappers. Put raw
unsafe extern
bindings in a small private module.
- Verify ABI, type layout, calling convention, ownership, nullability,
threading, and error conventions against the foreign header or docs.
- Use
unsafe extern "C" blocks for modern Rust, especially Rust 2024 edition
code. Leave safety-conditional items unqualified or explicitly unsafe, and
mark individual extern items safe only when calling them is safe for all
Rust inputs.
- Use
#[repr(C)] for structs and enums that cross the ABI boundary. Do not
expose Rust-only layout types across C.
- Use
CString for owned nul-terminated strings sent to C and CStr for
borrowed C strings. Never treat arbitrary C memory as Rust-owned.
- Wrap raw handles in Rust types with private fields and
Drop cleanup using
the matching foreign deallocator.
- Prevent Rust panics from crossing
extern "C" boundaries. Catch panics or
expose an ABI that permits unwinding only when deliberately using
"C-unwind".
Wrapper Rules
Read references/ffi-boundary-patterns.md before adding or reviewing FFI
bindings.
- Prefer
core::ffi or std::ffi C types over guessing integer sizes.
- Convert foreign error codes into Rust
Result at the wrapper boundary.
- Represent nullable handles as
Option<NonNull<T>> internally when useful,
but expose safe Rust types to callers.
- Tie borrowed values to owner lifetimes with normal references or
PhantomData when the compiler cannot see the relationship.
- Make initialization and shutdown idempotent with
OnceLock, LazyLock, or
Once when the foreign library requires process-wide setup.
- Use
bindgen for large or changing C headers and cbindgen when exporting a
Rust API to C, but still review generated unsafe signatures.
Rust 2024 Syntax Checks
unsafe extern "C" {
pub fn library_open(path: *const core::ffi::c_char) -> *mut RawHandle;
pub fn library_close(handle: *mut RawHandle);
// Mark an item `safe` only when calling it is valid for all Rust inputs:
// pub safe fn library_version() -> core::ffi::c_int;
}
// SAFETY: This exported symbol name is unique for this library.
#[unsafe(no_mangle)]
pub extern "C" fn plugin_version() -> core::ffi::c_int {
1
}
Unsafe attributes such as no_mangle, export_name, and link_section need
the unsafe(...) form in Rust 2024 edition code. Include a SAFETY comment
where symbol uniqueness or linker behavior is part of the contract.
Review Checklist
- Raw bindings are private or clearly separated from safe wrappers.
- Every pointer parameter has a documented nullability and ownership rule.
- Every allocation is freed by the same side or an explicitly matching
deallocator.
- Strings and buffers preserve length, encoding, and nul-byte requirements.
- Panics, callbacks, and threads crossing the boundary have explicit behavior.
1---2name: rust-ffi-safe-wrappers3description: Design and review Rust FFI boundaries that keep raw declarations isolated behind safe ownership, lifetime, error, and cleanup wrappers. Use when writing, refactoring, or reviewing unsafe extern blocks, C ABI wrappers, repr(C) types, CString, CStr, raw handles, Drop cleanup, bindgen, cbindgen, or panic-safe foreign boundaries.4license: MIT5compatibility: Codex, Claude Code, and other Agent Skills-compatible clients.6metadata:7 version: "0.1.0"8 displayName: Rust FFI Safe Wrappers9 category: Rust10 tags: rust,ffi,c-abi,unsafe,bindings11---1213# Rust FFI Safe Wrappers1415Use this skill to wrap foreign interfaces in Rust APIs that make ownership,16lifetimes, errors, and cleanup explicit. FFI code should be unsafe at the raw17boundary and safe for normal Rust callers.1819## Core Workflow20211. Separate raw declarations from safe wrappers. Put raw `unsafe extern`22 bindings in a small private module.232. Verify ABI, type layout, calling convention, ownership, nullability,24 threading, and error conventions against the foreign header or docs.253. Use `unsafe extern "C"` blocks for modern Rust, especially Rust 2024 edition26 code. Leave safety-conditional items unqualified or explicitly `unsafe`, and27 mark individual extern items `safe` only when calling them is safe for all28 Rust inputs.294. Use `#[repr(C)]` for structs and enums that cross the ABI boundary. Do not30 expose Rust-only layout types across C.315. Use `CString` for owned nul-terminated strings sent to C and `CStr` for32 borrowed C strings. Never treat arbitrary C memory as Rust-owned.336. Wrap raw handles in Rust types with private fields and `Drop` cleanup using34 the matching foreign deallocator.357. Prevent Rust panics from crossing `extern "C"` boundaries. Catch panics or36 expose an ABI that permits unwinding only when deliberately using37 `"C-unwind"`.3839## Wrapper Rules4041Read `references/ffi-boundary-patterns.md` before adding or reviewing FFI42bindings.4344- Prefer `core::ffi` or `std::ffi` C types over guessing integer sizes.45- Convert foreign error codes into Rust `Result` at the wrapper boundary.46- Represent nullable handles as `Option<NonNull<T>>` internally when useful,47 but expose safe Rust types to callers.48- Tie borrowed values to owner lifetimes with normal references or49 `PhantomData` when the compiler cannot see the relationship.50- Make initialization and shutdown idempotent with `OnceLock`, `LazyLock`, or51 `Once` when the foreign library requires process-wide setup.52- Use `bindgen` for large or changing C headers and `cbindgen` when exporting a53 Rust API to C, but still review generated unsafe signatures.5455## Rust 2024 Syntax Checks5657```rust58unsafe extern "C" {59 pub fn library_open(path: *const core::ffi::c_char) -> *mut RawHandle;60 pub fn library_close(handle: *mut RawHandle);61 // Mark an item `safe` only when calling it is valid for all Rust inputs:62 // pub safe fn library_version() -> core::ffi::c_int;63}6465// SAFETY: This exported symbol name is unique for this library.66#[unsafe(no_mangle)]67pub extern "C" fn plugin_version() -> core::ffi::c_int {68 169}70```7172Unsafe attributes such as `no_mangle`, `export_name`, and `link_section` need73the `unsafe(...)` form in Rust 2024 edition code. Include a `SAFETY` comment74where symbol uniqueness or linker behavior is part of the contract.7576## Review Checklist7778- Raw bindings are private or clearly separated from safe wrappers.79- Every pointer parameter has a documented nullability and ownership rule.80- Every allocation is freed by the same side or an explicitly matching81 deallocator.82- Strings and buffers preserve length, encoding, and nul-byte requirements.83- Panics, callbacks, and threads crossing the boundary have explicit behavior.