prebindgen 0.4.1

Separate FFI implementation and language-specific binding into different crates
Documentation
# prebindgen

A tool for separating the implementation of FFI interfaces from language-specific binding generation, allowing each to reside in different crates.

## Problem

Making FFI (Foreign Function Interface) for a Rust library is not an easy task. This involves a large amount of boilerplate code that wraps the Rust API in `extern "C"` functions and `#[repr(C)]` structures.
It's already hard for a single language, but when you need to add more languages, the situation becomes more complex.

The root cause of this complexity in a multi-language scenario is that you must either:

- Make separate crates for each language's FFI. For example, you have a Rust library "foo" and cdylib/staticlib crates "foo-c", "foo-csharp", "foo-java", etc. Each crate contains its own independent wrappers for "foo". Code duplication is huge.
- Generate all language-specific libraries from the same source. In this case, you just replace code duplication with code complexity: the single FFI library must conform to all language targets.

A small example: `cbindgen` supports wrapper types (`Option`, `MaybeUninit`) in `extern "C"` functions, but `csbindgen` (a binding generator for C#) doesn't understand them. The following FFI function works for C but can't be used for C#:

```rust
#[no_mangle]
pub extern "C" fn foo(dst: &mut MaybeUninit<Foo>, src: &Foo) { ...  }
```

And there are more such quirks, which make it hard to support a common source for multiple languages.

## Solution

The proposed solution is to create a common Rust library (e.g., "foo-ffi") that wraps the original "foo" library in FFI-compatible functions, but does not add `extern "C"` and `#[no_mangle]` modifiers. Instead, it marks these functions with the `#[prebindgen]` macro.

```rust
#[prebindgen]
pub fn foo(dst: &mut MaybeUninit<Foo>, src: &Foo) { ...  }
```

The dependent language-specific crates ("foo-c", "foo-cs", etc.) in this case contain only autogenerated code based on these marked functions, with the necessary `extern "C"` and `#[no_mangle]` added, stripped-out wrapper types, etc.

### Architecture

Each element to be exported is marked in the source crate with the `#[prebindgen]` macro. When the source crate is compiled, these elements are written to an output directory. The destination crate's `build.rs` reads these elements and creates FFI-compatible functions and proxy structures for them. The generated source file is included with the `include!()` macro in the dependent crate and parsed by the language binding generator (e.g., cbindgen).

It's important to keep in mind that `[build-dependencies]` and `[dependencies]` are different. The `#[prebindgen]` macro collects sources when compiling the `[build-dependencies]` instance of the source crate. Later, these sources are used to generate proxy calls to the `[dependencies]` instance, which may be built with a different feature set and for a different architecture. A set of assertions is added to the generated code to catch possible divergences, but it's the developer's job to manually resolve these errors.

## Usage

### 1. In the Common FFI Library Crate (e.g., `example-ffi`)

Mark structures and functions that are part of the FFI interface with the `prebindgen` macro and export the prebindgen output directory path:

```rust
// example-ffi/src/lib.rs
use prebindgen_proc_macro::prebindgen;

// Path to the prebindgen output directory; the destination crate's `build.rs`
// reads the collected code from this path.
pub const PREBINDGEN_OUT_DIR: &str = prebindgen_proc_macro::prebindgen_out_dir!();

// Features with which the crate is compiled. This constant is used
// in the generated code to validate that it's compatible with the actual crate.
pub const FEATURES: &str = prebindgen_proc_macro::features!();

// Group structures and functions for selective handling
#[prebindgen]
#[repr(C)]
pub struct MyStruct {
    pub field: i32,
}

#[prebindgen]
pub fn my_function(arg: i32) -> i32 {
    arg * 2
}
```

Call `init_prebindgen_out_dir()` in the source crate's `build.rs`:

```rust
// example-ffi/build.rs
fn main() {
    prebindgen::init_prebindgen_out_dir();
}
```

### 2. In the Language-Specific FFI Binding Crate (e.g., `example-cbindgen`)

Add the source FFI library to both dependencies and build-dependencies:

```toml
# example-cbindgen/Cargo.toml
[dependencies]
example_ffi = { path = "../example_ffi" }

[build-dependencies]
example_ffi = { path = "../example_ffi" }
prebindgen = "0.4"
cbindgen = "0.29"
itertools = "0.14"
```

Convert `#[prebindgen]`-marked items to an FFI-compatible API (`repr(C)` structures, `extern "C"` functions, constants). Items that are not valid for FFI will be rejected by `FfiConverter`.

Generate target language bindings based on this source.

Custom filters can be applied if necessary.

```rust
// example-cbindgen/build.rs
use itertools::Itertools;

fn main() {
    // Create a source from the common FFI crate's prebindgen data
    let source = prebindgen::Source::new(example_ffi::PREBINDGEN_OUT_DIR);

    // Create a converter with transparent wrapper stripping
    let converter = prebindgen::batching::FfiConverter::builder(source.crate_name())
        .edition(prebindgen::RustEdition::Edition2024)
        .strip_transparent_wrapper("std::mem::MaybeUninit")
        .strip_transparent_wrapper("std::option::Option")
        .prefixed_exported_type("foo::Foo")
        .build();

    // Process items with filtering and conversion
    let bindings_file = source
        .items_all()
        .batching(converter.into_closure())
        .collect::<prebindgen::collect::Destination>()
        .write("ffi_bindings.rs");

    // Pass the generated file to cbindgen for C header generation
    generate_c_headers(&bindings_file);
}
```

Include the generated Rust file in your project to build the static or dynamic FFI-compatible library:

```rust
// lib.rs
include!(concat!(env!("OUT_DIR"), "/ffi_bindings.rs"));
```

## Examples

See example projects in the [examples directory](https://github.com/milyin/prebindgen/tree/main/examples):

- **example-ffi**: Common FFI library demonstrating prebindgen usage
- **example-cbindgen**: Language-specific binding using cbindgen for C headers

## Documentation

- **prebindgen API Reference**: [docs.rs/prebindgen](https://docs.rs/prebindgen)
- **prebindgen-proc-macro API Reference**: [docs.rs/prebindgen-proc-macro](https://docs.rs/prebindgen-proc-macro)