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

error
macros
FFI Helper Macros
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_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. Returns null_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