Skip to content
Back to skills

Ffi Boundaries

ASecurity

Design foreign-function boundaries with explicit ownership, error translation, and contained panics/exceptions. Use when binding between Rust/Go/Python and C/C++ libraries or exposing your library over a C ABI.

  • 7 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 5, 2026
ai-agentspythonrustgoc++code-reviewperformancedocumentation

Works with

  • cli

Security analysis

A100/100

Scanned September 5, 2026

npx -y skills add Amey-Thakur/AI-SKILLS --skill ffi-boundaries --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ffi Boundaries?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ffi Boundaries
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/amey-thakur-ffi-boundaries/badge)](https://www.skillsdirectory.com/skills/amey-thakur-ffi-boundaries)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ffi-boundaries
description: Design foreign-function boundaries with explicit ownership, error translation, and contained panics/exceptions. Use when binding between Rust/Go/Python and C/C++ libraries or exposing your library over a C ABI.
---

# FFI boundaries

The C ABI is the lingua franca and it carries no safety: no ownership,
no lifetimes, no exceptions, no strings with lengths. Everything the
type systems on either side enforced must be re-established by
convention at the boundary, in writing.

## Method

1. **Write the contract per function.** For every crossing: who
   allocates, who frees, with which allocator (never free across
   allocator boundaries: the library provides `x_free()` for what
   `x_create()` returned); pointer validity duration (call-scoped or
   retained?); nullability of every pointer; thread-safety
   (callable from any thread? reentrant?). This contract is the
   SAFETY documentation the wrapper cites (see unsafe-code-review).
2. **Keep the boundary narrow and C-plain.** Cross with C types
   only: integers, raw pointers, length-tagged buffers
   (`ptr + len`, never assume NUL-termination), opaque handles
   (`struct Foo*` the other side never dereferences). Convert to
   rich native types (String, slices, structs) immediately inside
   the wrapper; business logic never touches boundary types.
3. **Translate errors at the edge.** C side: return codes plus a
   `last_error`/out-param message. Native side: convert codes into
   the language's error idiom (Result variants, exceptions,
   Go errors) with context (see rust-error-handling); never let
   callers see raw negative ints. Exposing your library: catch
   every panic/exception *at* the boundary
   (`catch_unwind`, `try/catch`) and return a code: unwinding across
   the C ABI is undefined behavior.
4. **Handle callbacks with double care.** A callback into native
   code carries: a `user_data` context pointer (kept alive by the
   caller for the registration's whole duration: document it),
   no unwinding out (wrap the body in the panic barrier), and a
   stated threading model (which thread calls it, may it re-enter
   the library?). Callback lifetime bugs are FFI's signature
   use-after-free.
5. **Automate the bindings, own the safe layer.** Generate raw
   bindings (bindgen, cgo, cffi-class tooling) so signatures track
   the header; hand-write the one safe wrapper layer that enforces
   the contract (RAII/Drop guards for handles: see cpp-raii,
   c-memory-safety pairing rules). Two layers, clearly separated:
   generated-unsafe and handwritten-safe.
6. **Test the boundary itself.** Round-trip tests for strings/buffers
   with embedded NULs and non-UTF8 bytes; leak tests cycling
   create/destroy under ASan/Valgrind (see memory-leaks); a
   double-free and use-after-free test proving your guards make them
   impossible; threaded smoke tests if the contract claims safety.
   Run under sanitizers in CI; FFI is exactly where they earn their
   keep.

## Boundaries

- Marshaling costs real time on chatty interfaces; batch crossings
  (arrays in, arrays out) rather than per-item calls when profiles
  say the boundary dominates (see python-performance step 5).
- Struct layout across the boundary requires repr(C)/packing
  agreement and versioning discipline; passing structs by value
  across ABIs with different compilers is a compatibility bug farm:
  prefer opaque handles plus accessors.
- Language-specific mechanisms (Go's cgo pointer rules, Python's
  GIL during callbacks) add rules on top of this skill; read the
  runtime's FFI documentation as part of the contract.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…