Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ name = "cimpl"
readme = "README.md"
repository = "https://github.com/contentauth/cimpl"
rust-version = "1.87"
version = "0.4.0"
version = "0.4.1"

[package.metadata.docs.rs]
all-features = true
Expand Down
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Simple C implementations from Rust**

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

[![Crates.io](https://img.shields.io/crates/v/cimpl.svg)](https://crates.io/crates/cimpl)
[![Documentation](https://docs.rs/cimpl/badge.svg)](https://docs.rs/cimpl)
Expand All @@ -18,9 +18,11 @@ 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 show trivial toy code:
Most Rust FFI examples are trivial examples:
```rust
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b }
Expand All @@ -33,7 +35,7 @@ pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b }
- How do you prevent memory leaks and double-frees?
- How do you make errors usable in other languages?

**cimpl solves the hard problems:**
**cimpl helps with the hard problems:**
- Type-safe pointer tracking with validation
- Automatic error handling with descriptive, parseable messages
- Memory leak detection in tests
Expand Down Expand Up @@ -84,7 +86,7 @@ pub extern "C" fn vc_free(ptr: *mut c_void) -> i32 {
}
```

**That's it!** From this simple code:
**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"`
Expand Down Expand Up @@ -136,6 +138,7 @@ except OutOfRangeError as e:
- **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
Expand Down Expand Up @@ -184,7 +187,7 @@ cbindgen = "0.27"
- **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, leak detection
- **Memory safety**: Tracked allocations, type validation
- **Clear separation**: `lib.rs` (pure Rust API) vs `ffi.rs` (C FFI wrapper)

**Two-file structure:**
Expand All @@ -197,7 +200,7 @@ See [examples/reference/README.md](./examples/reference/README.md) for detailed

## Real-World Use

A variation of this pattern used in production at Adobe for the [C2PA project](https://github.com/contentauth/c2pa-rs), providing C, Python, and other language bindings from a single Rust codebase.
This is used in production for the [C2PA project](https://github.com/contentauth/c2pa-rs), providing C++, Python, Swift, Kotlin and other language bindings from a single Rust codebase.

## Contributing

Expand All @@ -210,3 +213,8 @@ Licensed under either of:
- MIT license ([LICENSE-MIT](LICENSE-MIT))

at your option.

[GitHub repository](https://github.com/contentauth/cimpl)



24 changes: 7 additions & 17 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ use crate::{
// ============================================================================
// Pointer Registry - Tracks pointers with their cleanup functions
// ============================================================================

type CleanupFn = Box<dyn FnMut() + Send>;

/// Type for tracked allocations:
Expand Down Expand Up @@ -647,7 +646,7 @@ impl PointerRegistry {
/// Use this whenever an FFI function consumes a tracked pointer via
/// `Box::from_raw()`: the pointer must be untracked first,
/// or the registry holds a stale entry that double-frees on
/// `cimpl_free()` or reports a false leak at shutdown.
/// `cimpl_free()`.
///
/// # When to use
///
Expand Down Expand Up @@ -855,22 +854,13 @@ impl PointerRegistry {
}
}

/// Automatic leak detection at shutdown.
///
/// When the pointer registry is dropped (at program shutdown), it checks for any
/// tracked pointers that were never freed. This helps identify memory leaks caused
/// by missing `cimpl_free()` calls in C code.
///
/// # Example Output
/// Reports outstanding entries when a registry instance is dropped.
///
/// ```text
/// ⚠️ WARNING: 3 pointer(s) were not freed at shutdown!
/// This indicates C code did not properly free all allocated pointers.
/// Each pointer should be freed exactly once with cimpl_free().
/// ```
/// The global registry is a static and is not dropped at process exit,
/// so this does not provide automatic shutdown leak reporting.
///
/// This detection runs in **all builds** (debug, release, and test) to help catch
/// memory management bugs during development and integration testing.
/// Cleanup is canceled for remaining entries to avoid destroying objects
/// that callers may still reference.
impl Drop for PointerRegistry {
fn drop(&mut self) {
if self.owner_pid != current_pid() {
Expand Down Expand Up @@ -1124,7 +1114,7 @@ pub fn untrack_owned<T: 'static>(ptr: *mut T) -> Result<T, Error> {
/// // Handle error
/// }
/// ```
pub extern "C" fn cimpl_free(ptr: *mut std::ffi::c_void) -> i32 {
pub fn cimpl_free(ptr: *mut std::ffi::c_void) -> i32 {
match get_registry().free(ptr as usize) {
Ok(()) => 0,
Err(error) => {
Expand Down
Loading