# 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)