Skip to content

cimpl

Simple C implementations from Rust

Create clean, safe C FFI bindings for Rust that AI can automatically convert to any language.

Crates.io Documentation License

Note: This is a new project (v0.3.0+) taking over the cimpl crate name. Previous versions (0.1.x, 0.2.0) were a different, unrelated project and have been yanked.

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, and let AI generate high-quality bindings for Python, JavaScript, Lua, Ruby, C#, Java, Go, and more.

There is no special binding language and you don't have to annotate your code. This is important when you need to support more than one language.

Why cimpl?

Most Rust FFI examples are trivial examples:

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b }

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 helps with 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

Note: For Node.js and WASM targets, use wasm-bindgen instead. While Node.js can use cimpl via Koffi FFI, WASM provides better performance and integration.

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 vc_to_string(value: *mut ValueConverter) -> *mut c_char {
    let converter = deref_or_return_null!(value, ValueConverter);
    let result = ok_or_return_null!(converter.to_string());  // Error → cimpl::Error automatically
    to_c_string(result)
}

// Memory management wrapper (required for namespace safety)
#[no_mangle]
pub extern "C" fn vc_free(ptr: *mut c_void) -> i32 {
    cimpl::cimpl_free(ptr)  // Rust-level function, wrap in your C API
}

That's it! From this 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

AI Users: See AI_WORKFLOW.md for a complete guide to generating FFI wrappers and language bindings with proper macro usage patterns.

What You Get

From Rust to C

#include "value_converter.h"

// Create from integer
ValueConverter* vc = vc_from_i32(42);
if (vc == NULL) {
    char* msg = vc_last_error();  // "OutOfRange: need exactly 4 bytes..."
    printf("Error: %s\n", msg);
    vc_free(msg);
    return -1;
}

// Convert to hex
char* hex = vc_to_hex(vc);
printf("Hex: %s\n", hex);  // "2a000000"

// Cleanup (namespace-safe free function)
vc_free(hex);
vc_free(vc);

From C to Python (auto-generated!)

from value_converter import ValueConverter, OutOfRangeError

try:
    with ValueConverter.from_i32(42) as vc:
        print(vc.to_hex())  # "2a000000"
except OutOfRangeError as e:
    print(f"Error: {e}")

Features

Pointer Safety

  • Tracked pointers with type validation
  • Rust-level cimpl::cimpl_free() function (wrap with {crate}_free in your C API)
  • Shared registry across all cimpl-based libraries
  • Double-free protection
  • Type mismatch detection
  • Allocation tracking with double-free and type-mismatch detection

Error Handling

  • String-based error messages with consistent "VariantName: details" format
  • AI-friendly format: Easy to parse and convert to typed exceptions
  • Automatic conversion via cimpl::Error::from_error() or manual with cimpl::Error::new()
  • Works with thiserror: Use #[derive(ThisError)] for ergonomic error definitions
  • Standard C conventions: NULL/false/-1 indicates error, call *_last_error() for details

Clean Macros

  • box_tracked!() - Allocate and track Box
  • ptr_or_return!() - Null pointer checks with automatic error messages
  • bytes_or_return_*!() - Byte array validation with bounds checking
  • cstr_or_return_*!() - C string conversion with null checks
  • deref_or_return_*!() - Pointer validation and dereferencing
  • ok_or_return_*!() - Result unwrapping with automatic error conversion
  • option_to_c_string!() - Option to C string (NULL if None)

Getting Started

Add to your Cargo.toml:

[dependencies]
cimpl = "0.4"
thiserror = "2.0"  # Recommended for ergonomic errors

[build-dependencies]
cbindgen = "0.27"

For AI Users

Read AI_WORKFLOW.md first! It contains:

  • 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

Example

examples/reference/ - Production-ready reference implementation

This is not a toy example. Unlike the typical add(a, b) FFI tutorials, this demonstrates the hard parts:

  • Real-world utility: Value converter for type conversion (i32, u32, i64, u64, bytes, strings, hex)
  • Multiple constructors: from_i32(), from_string(), from_hex(), etc.
  • Fallible conversions: to_i32() might fail (wrong size), to_string() might fail (invalid UTF-8)
  • Proper validation: Size limits, UTF-8 checks, overflow detection
  • Memory safety: Tracked allocations, type validation
  • Clear separation: lib.rs (pure Rust API) vs ffi.rs (C FFI wrapper)

Two-file structure:

  • src/lib.rs - Standard Rust library (no FFI concerns)
  • src/ffi.rs - C FFI wrapper using cimpl

This shows how to add FFI to an existing Rust library, not how to write FFI from scratch.

See examples/reference/README.md for detailed API reference.

Real-World Use

This is used in production for the C2PA project, providing C++, Python, Swift, Kotlin and other language bindings from a single Rust codebase.

Contributing

Contributions welcome! This project is about making FFI simple and accessible.

License

Licensed under either of:

at your option.

GitHub repository

About

A safe way to expose complex Rust code via C_FFI

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages