Expand description
§cimpl - Simple C implementations from Rust
Create clean, safe C FFI bindings that AI can automatically convert to any language.
§The Vision
Rust + cimpl → Clean C API → AI-powered bindings → All languagesWrite your library once in safe Rust, expose it through a clean C API using cimpl’s macros, and let AI generate high-quality bindings for Python, JavaScript, Lua, Ruby, C#, Java, Go, and more.
§Why cimpl?
This library emerged from real-world challenges with FFI in the c2pa-rs project at Adobe. After experimenting with various Rust language binding tools, I found they all required decorating Rust code with language-specific annotations or special interface definition languages, generating volumes of incomprehensible glue code in the process.
Then came the insight: AI excels at writing bindings for well-documented C APIs. Rust natively supports C APIs, but writing them manually is tricky and error-prone. So cimpl was born - a library of macros and utilities that makes it safe and maintainable to write C bindings from Rust.
The result? Given this library, AI can generate both the C FFI bindings AND the language-specific bindings automatically. The UUID example in this crate was generated entirely by AI in 15 minutes with zero compilation errors, proving the concept works in practice. Look at the code. Everything generated, from the C header files to to language bindings is well documented and readable. There is no incomprehensible glue code.
Look at the examples in the examples directory. They are real-world examples of how to use cimpl to write
safe and maintainable C bindings. The UUID example was generated entirely by AI in 15 minutes with zero compilation
errors. The ValueConverter example shows patterns and how to use them.
Most Rust FFI examples are trivial. Real FFI is much harder:
- How do you return complex types like strings or structs?
- How do you propagate
Result<T, E>errors across the FFI boundary? - How do you handle object lifecycle (constructors, methods, destructors)?
- How do you prevent memory leaks and double-frees?
- How do you make errors usable in other languages?
cimpl solves the hard problems:
- ✅ Type-safe pointer tracking with validation
- ✅ Automatic error handling with descriptive, parseable messages
- ✅ Memory leak detection in tests
- ✅ Clean macros for production patterns (not toy examples)
- ✅ Object-oriented APIs (structs with methods, not just functions)
- ✅ AI-friendly C headers (auto-generated via cbindgen)
- ✅ One codebase → many language bindings
§Quick Example
use cimpl::*;
use std::ffi::c_void;
use std::os::raw::c_char;
use thiserror::Error as ThisError;
// Your library's error type (using thiserror for convenience)
#[derive(ThisError, Debug)]
pub enum Error {
#[error("value out of range: {0}")]
OutOfRange(String),
#[error("invalid UTF-8: {0}")]
InvalidUtf8(String),
}
// Map to cimpl::Error for FFI - one line with from_error()!
impl From<Error> for cimpl::Error {
fn from(e: Error) -> Self {
cimpl::Error::from_error(e) // Automatic: Debug → variant, Display → message
}
}
// Clean, safe FFI function
#[no_mangle]
pub extern "C" fn process_value(ptr: *mut MyType) -> *mut c_char {
let obj = deref_or_return_null!(ptr, MyType);
let result = ok_or_return_null!(obj.to_string()); // Error → cimpl::Error automatically
to_c_string(result)
}
// Memory management wrapper (required for namespace safety)
#[no_mangle]
pub extern "C" fn my_free(ptr: *mut c_void) -> i32 {
cimpl::cimpl_free(ptr) // Rust-level function, wrap in your C API
}That’s it! From this simple code:
- cbindgen generates a C header with proper namespace prefix
- Type validation ensures safety
- Errors map to descriptive strings:
"VariantName: details" - Memory is tracked automatically
- AI can generate bindings for any language
§Core Features
§String-Based Error Handling
Errors use a consistent "VariantName: details" format that works across all languages:
use cimpl::Error;
// Create errors manually
let err = Error::new("OutOfRange", "value must be between 0 and 100");
// Or convert automatically from any std::error::Error
let err = Error::from_error(my_error); // Uses Debug for variant, Display for messageThis format is:
- ✅ Human-readable (developers)
- ✅ Machine-parseable (error handlers)
- ✅ AI-friendly (code generation)
- ✅ Cross-language (works in C, Python, Ruby, Swift, Go…)
§Pointer Safety Macros
box_tracked!- Allocate and track Boxptr_or_return!- Null pointer checks with automatic error messagesderef_or_return!- Pointer validation and dereferencing (immutable)deref_mut_or_return!- Pointer validation and dereferencing (mutable)
§String Conversion
cstr_or_return!- C string to Rust with UTF-8 validation and bounds checkingto_c_string()- Rust String to tracked C stringoption_to_c_string!- Optionto C string (NULL if None)
§Byte Array Handling
bytes_or_return!- Validate and convert C byte arraysto_c_bytes()- Rust Vecto tracked C byte array
§Result Handling
ok_or_return!- Result unwrapping with automatic error conversionok_or_return_null!- Unwrap Result, return NULL on errorok_or_return_false!- Unwrap Result, return false on error- Works with any error type implementing
Into<cimpl::Error>
§Option Handling
some_or_return!- Option unwrapping with custom errorssome_or_return_other_null!- Option with Error::other message, return NULL
§Memory Management
All pointers allocated via box_tracked!, arc_tracked!, or the tracking functions are
registered in a global, thread-safe registry. Each library should wrap cimpl_free():
#[no_mangle]
pub extern "C" fn mylib_free(ptr: *mut c_void) -> i32 {
cimpl::cimpl_free(ptr)
}This provides:
- Namespace safety: No symbol conflicts when linking multiple libraries
- Type validation: Wrong type returns error instead of crashing
- Double-free protection: Registry prevents freeing the same pointer twice
- Leak detection: Unfreed pointers reported at program exit
§AI-Friendly Design
cimpl is designed to enable AI code generation. See AI_WORKFLOW.md in the repository for:
- Pre-flight checklist for catching anti-patterns
- Complete macro reference with decision trees
- Common mistakes to avoid
- Step-by-step guidance for generating FFI code
Proven: The UUID example was generated entirely by AI in 15 minutes with zero errors.
§Examples
The crate includes two complete, production-ready examples:
-
examples/reference/- ValueConverter showing all FFI patterns- Clean lib.rs/ffi.rs separation
- Full Python bindings with ctypes
- Demonstrates struct methods, constructors, error handling
-
examples/uuid/- Real-world external crate wrapping- 15 FFI functions exposing uuid crate
- Complete Python bindings
- AI-generated in 15 minutes from documentation
- Demonstrates direct external crate usage pattern
§Philosophy
See PHILOSOPHY.md in the repository for the complete design rationale. Key insights:
- The C ABI is timeless - Build on solid ground
- Safety through validation - Not through complexity
- Standard C conventions - Developers know what to expect
- Universal patterns - One way to do things
- AI-friendly design - Enable code generation
- Language independence - Your C API outlives any specific tooling
Build once in Rust. Expose through C. Use everywhere.
Re-exports§
pub use error::Error;pub use error::Result;pub use error::Error as CimplError;pub use utils::cimpl_free;pub use utils::safe_slice_from_raw_parts;pub use utils::to_c_bytes;pub use utils::to_c_string;pub use utils::track_arc;pub use utils::track_arc_mutex;pub use utils::track_box;
Modules§
Macros§
- arc_
tracked - Create an Arc-wrapped pointer and track it Returns the raw pointer
- box_
tracked - Create a Box-wrapped pointer and track it Returns the raw pointer
- bytes_
or_ return - Validate and convert raw C byte array to safe slice, returning early on error.
- bytes_
or_ return_ int - Validate and convert raw C byte array to safe slice, return -1 on error.
- bytes_
or_ return_ null - Validate and convert raw C byte array to safe slice, return NULL on error.
- cimpl_
free - Free a pointer that was allocated by cimpl.
- cstr_
option - cstr_
or_ return - Convert C string with bounded length check or early-return with error value Uses a safe bounded approach to prevent reading unbounded memory. Maximum string length is MAX_CSTRING_LEN (1MB).
- cstr_
or_ return_ int - cstr_
or_ return_ null - If the expression is null, set the last error and return std::ptr::null_mut().
- cstr_
or_ return_ with_ limit - Convert C string with custom length limit or early-return with error value Allows specifying a custom maximum length for the string.
- deref_
mut_ or_ return - Validate pointer and dereference mutably, returning reference Returns early with custom value on error
- deref_
mut_ or_ return_ int - Validate pointer and dereference mutably, returning reference Returns -1 on error
- deref_
mut_ or_ return_ null - Validate pointer and dereference mutably, returning reference Returns NULL on error
- deref_
or_ return - Validate pointer and dereference immutably, returning reference Returns early with custom value on error
- deref_
or_ return_ false - Validate pointer and dereference immutably, returning reference Returns false on error
- deref_
or_ return_ int - Validate pointer and dereference immutably, returning reference Returns -1 on error
- deref_
or_ return_ null - Validate pointer and dereference immutably, returning reference Returns NULL on error
- deref_
or_ return_ zero - Validate pointer and dereference immutably, returning reference Returns 0 on error
- ok_
or_ return - Handle Result or early-return with error value
- ok_
or_ return_ false - Handle Result, early-return with false on error
- ok_
or_ return_ int - Handle Result, early-return with -1 (negative) on error
- ok_
or_ return_ null - Handle Result, early-return with null on error
- ok_
or_ return_ zero - Handle Result, early-return with 0 on error
- option_
to_ c_ string - Converts an
Option<String>to a C string pointer. Returnsnull_mut()if the Option is None. - ptr_
or_ return - Check pointer not null or early-return with error value
- ptr_
or_ return_ int - If the expression is null, set the last error and return -1.
- ptr_
or_ return_ null - If the expression is null, set the last error and return null.
- some_
or_ return - Handle Option, early-return with custom value if None
- some_
or_ return_ false - Handle Option, early-return with false if None
- some_
or_ return_ int - Handle Option, early-return with -1 if None
- some_
or_ return_ null - Handle Option, early-return with NULL if None
- some_
or_ return_ other_ false - Convenience macro: Handle Option with Error::other message, return false
- some_
or_ return_ other_ int - Convenience macro: Handle Option with Error::other message, return -1
- some_
or_ return_ other_ null - Convenience macro: Handle Option with Error::other message
- some_
or_ return_ other_ zero - Convenience macro: Handle Option with Error::other message, return 0
- some_
or_ return_ zero - Handle Option, early-return with 0 if None