Skip to main content

Crate prebindgen

Crate prebindgen 

Source
Expand description

§prebindgen

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

See also: prebindgen-proc-macro for the procedural macros.

§Problem

When creating Rust libraries that need to expose FFI interfaces to multiple languages, it may be preferable to create separate cdylib or staticlib crates for each language-specific binding. This allows you to tailor each crate to the requirements and quirks of its binding generator and to specifisc of the destination language. However, #[no_mangle] extern "C" functions can only be defined in a cdylib or staticlib crate, and cannot be exported from a lib crate. As a result, these functions must be duplicated in each language-specific binding crate. This duplication is inconvenient for large projects with many FFI functions and types.

§Solution

prebindgen solves this by generating language-specific proxy code from a common Rust library crate. This crate is the base of that pipeline: it reads what #[prebindgen] captured and hands out (syn::Item, [SourceLocation]) pairs through Source — nothing more. The flat model built over that stream ships in the separate prebindgen-flat crate; the language-neutral registry pipeline over the flat model — type resolution, boundary expansion, Rust emission — ships in the separate prebindgen-registry crate. The supported 0.5 surface is that pipeline plus the JNI/Kotlin JniGenBuilder adapter, which ships in the separate prebindgen-jni crate.

The C / cbindgen adapter is an experimental proof of concept, ships in the separate prebindgen-c crate, and is not covered by the 0.5 semver guarantee.

§Usage example

See also example projects on https://github.com/milyin/prebindgen/tree/main/examples

See also the prebindgen-proc-macro documentation for details on how to use the #[prebindgen] macro: https://docs.rs/prebindgen-proc-macro/latest/prebindgen_proc_macro/

§Stable core and JNI/Kotlin path

The supported workflow reads captured items with Source, resolves them through the separate prebindgen-registry crate’s Registry, and configures the separate prebindgen-jni crate’s JniGenBuilder to emit Rust JNI wrappers plus Kotlin sources. The covertest-kotlin and perftest-kotlin workspace examples are the maintained references for that path; see prebindgen-jni’s own crate docs for the full JNI/Kotlin workflow and macro surface.

§1. In the Common FFI Library Crate (e.g., example_flat)

Mark the types and functions that form your FFI surface with the prebindgen macro. The source crate stays plain idiomatic Rust — opaque handles are ordinary types returned by value, fallible calls return Result<T, E>; the language adapter does the C-ABI lowering, so there is no #[repr(C)] here.

// example-flat/src/lib.rs
use prebindgen_proc_macro::{features, prebindgen, prebindgen_out_dir};

// Export the prebindgen output directory and the enabled features.
pub const PREBINDGEN_OUT_DIR: &str = prebindgen_out_dir!();
pub const FEATURES: &str = features!();

// An opaque handle — a plain Rust type, returned by value.
pub struct Calculator { value: f64 }

#[prebindgen]
pub fn calculator_new() -> Calculator { Calculator { value: 0.0 } }

#[prebindgen]
pub fn calculator_get_value(c: &Calculator) -> f64 { c.value }

Call init_prebindgen_out_dir in the crate’s build.rs:

// example-flat/build.rs
prebindgen::init_prebindgen_out_dir();

§2. Experimental C binding crate

Depend on the common FFI library (as both a normal and a build dependency) and drive the experimental prebindgen-c crate’s CbindgenBuilder adapter from build.rs:

# example-cbindgen/Cargo.toml
[dependencies]
example-flat = { path = "../example-flat" }
prebindgen = "0.5"
prebindgen-c-runtime = "0.5"   # the generated converters reference its traits
konst = "0.3"      # the generated file emits a konst feature guard

[build-dependencies]
example-flat = { path = "../example-flat" }
prebindgen = "0.5"
prebindgen-registry = "0.5"
prebindgen-c = "0.5"
cbindgen = "0.29"
syn = { version = "2", features = ["full"] }
// example-cbindgen/build.rs
use syn::parse_quote as pq;

fn main() {
    // Configure the C adapter: point it at the items captured from the
    // common FFI crate, and declare which ones to export and how to name them.
    let cbindgen = prebindgen_c::Cbindgen::builder()
        .source(example_flat::PREBINDGEN_OUT_DIR)
        .source_module(pq!(example_flat))
        .free_memory_function("example_free")
        .mangle_type_name(|base| format!("{base}_t"))
        .mangle_destructor(|base| format!("{base}_drop"))
        .mangle_function(|n| n.to_string())
        .opaque_ptr(pq!(Calculator))
        .function(pq!(calculator_new))
        .function(pq!(calculator_get_value)).panic();

    // Resolve types, then write the Rust file of `extern "C"` wrappers.
    let bindings_file = cbindgen.build().unwrap().write_rust("example_flat.rs").unwrap();

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

Include the generated Rust file in your crate to build the static or dynamic library:

// lib.rs
include!(concat!(env!("OUT_DIR"), "/example_flat.rs"));

§Macros

The declaration surface is built almost entirely from exported macros. The language-neutral ones — the domain vocabulary shared by every adapter, plus the syntax helpers they’re built from — now live in the separate prebindgen-registry crate:

  • Members & constants: fun!
  • Conversions: convert!, from!, try_from!, into!, try_into!
  • Boundary expansion: expand_param!, expand_return!, fields!

Syntax helpers produce a bare syn node — Type / Path / Expr / Signature / Ident — to hand to a declaration method that requires one. They exist only to sidestep syn::parse_quote!’s type-inference ambiguity (E0283) in a generic argument position, not to express a domain concept: ty!, path!, expr!, sig!, ident!.

The JNI/Kotlin-specific declaration macros — package!, ptr_class!, data_class!, enum_class!, sealed_class!, variant!, constant! — construct a typed *Decl for the Kotlin surface and live in the separate prebindgen-jni crate, which hands the result to its JniGenBuilder.

Modules§

utils

Macros§

trace
Macro for debug tracing in build.rs. Used by prebindgen-proc-macro to display paths to generated files, but can be also used in other contexts.

Structs§

Source
Provides access to prebindgen data generated by the #[prebindgen] macro
TargetTriple
TargetTriple is a small utility around target_lexicon::Triple with helpers to access parts and to convert into Rust cfg tokens.

Enums§

RustEdition
Rust edition for code generation

Constants§

DEFAULT_GROUP_NAME
Default group name for items without explicit group name

Functions§

get_all_features
Read all feature names declared in the current crate’s Cargo.toml.
get_enabled_features
Filter the full features list to only those that are currently enabled.
get_prebindgen_out_dir
Get the full path to the prebindgen output directory in OUT_DIR.
init_prebindgen_out_dir
Initialize the prebindgen output directory for the current crate
is_feature_enabled
Check whether a feature is enabled by looking at the corresponding CARGO_FEATURE_<NAME> environment variable provided to build scripts by Cargo. Hyphens in feature names are converted to underscores to match Cargo’s env var format.