Skip to main content

Crate cimpl

Crate cimpl 

Source
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 languages

Write 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 message

This 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

§String Conversion

§Byte Array Handling

§Result Handling

§Option Handling

§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:

  1. The C ABI is timeless - Build on solid ground
  2. Safety through validation - Not through complexity
  3. Standard C conventions - Developers know what to expect
  4. Universal patterns - One way to do things
  5. AI-friendly design - Enable code generation
  6. Language independence - Your C API outlives any specific tooling

Build once in Rust. Expose through C. Use everywhere.

Re-exports§

pub use error::Error as CimplError;
pub use error::Error;
pub use error::Result;
pub use utils::checkout_exclusive;
pub use utils::checkout_shared;
pub use utils::cimpl_free;
pub use utils::ensure_trackable;
pub use utils::is_safe_buffer_size;
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;
pub use utils::track_string_array;
pub use utils::untrack_owned;
pub use utils::untrack_owned_pair;
pub use utils::ExclusiveCheckout;
pub use utils::SharedCheckout;
pub use utils::TypedExclusive;
pub use utils::TypedShared;

Modules§

error
macros
FFI Helper Macros
maybe_send_sync
Conditional Send/Sync for WASM compatibility. On wasm32 these traits are no-ops so types don’t need to implement Send/Sync.
utils
FFI Utilities

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_array_or_return
Convert a NULL-terminated C string array (*const *const c_char) to a Vec<String>, or early-return with a custom error value.
cstr_array_or_return_int
Convert a NULL-terminated C string array to a Vec<String>, returning -1 on error.
cstr_array_or_return_null
Convert a NULL-terminated C string array to a Vec<String>, returning NULL on error.
cstr_option
Convert a *const c_char to Option<String>. Returns None if the pointer is null. Returns Some(String) if the pointer is not null. Returns None if the string is too long.
cstr_or_return
Convert C string with bounded length check or early-return with error value. Errors if the string exceeds MAX_CSTRING_LEN bytes.
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. Errors if the string exceeds max_len bytes.
deref_mut_option
Resolve a tracked pointer to its real address and dereference it mutably, returning None for NULL, untracked, or wrong-type pointers — no error is set, no early return happens.
deref_mut_option_or_return
Borrow the handle/object for a write operation, return None on NULL pointers. NULL in this case is not a showstopper error, since nulls may represent optional parameters.
deref_mut_option_or_return_int
As deref_mut_option_or_return!, returning -1.
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
distinct_or_return
Two stream arguments naming the same handle would check it out twice and block until the borrow deadline, then fail with the wrong error. Compare first and fail immediately instead.
distinct_or_return_int
As distinct_or_return!, returning -1.
distinct_or_return_null
As distinct_or_return!, returning null.
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. Returns null_mut() if the Option is None.
out_bytes_or_return_int
Gets a buffer to the caller as out-parameter (returns the length). Returns -1 if the bytes could not be allocated.
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
untrack_or_return
Untrack a pointer from the registry and take ownership of the pointee (ownership transfer from C to Rust), yielding it by value.
untrack_or_return_int
Untrack a pointer and take ownership of the pointee, returning -1 on error.
untrack_or_return_null
Untrack a pointer and take ownership of the pointee, returning NULL on error.