Skip to main content

Prebindgen

Trait Prebindgen 

Source
pub trait Prebindgen {
    type Metadata: Clone + Default;

    // Required methods
    fn on_function(
        &self,
        f: &Function,
        registry: &Registry<Self::Metadata>,
        emit: &Emit,
    ) -> TokenStream;
    fn on_struct(
        &self,
        s: &Struct,
        registry: &Registry<Self::Metadata>,
        emit: &Emit,
    ) -> TokenStream;
    fn on_variant(
        &self,
        v: &Variant,
        registry: &Registry<Self::Metadata>,
        emit: &Emit,
    ) -> TokenStream;
    fn on_enum(
        &self,
        e: &Enum,
        registry: &Registry<Self::Metadata>,
        emit: &Emit,
    ) -> TokenStream;

    // Provided methods
    fn prerequisites(
        &self,
        _registry: &Registry<Self::Metadata>,
        _emit: &Emit,
    ) -> Vec<Item> { ... }
    fn post_process_item(
        &self,
        _item: &mut Item,
        _registry: &Registry<Self::Metadata>,
        _emit: &Emit,
    ) { ... }
    fn validate(
        &self,
        _binding: &Building<'_, Self::Metadata>,
    ) -> Result<(), String> { ... }
    fn validate_resolved(
        &self,
        _registry: &Registry<Self::Metadata>,
    ) -> Result<(), String> { ... }
    fn source_module(&self) -> Option<&Path> { ... }
    fn on_const(
        &self,
        c: &Constant,
        _registry: &Registry<Self::Metadata>,
        emit: &Emit,
    ) -> TokenStream { ... }
}
Expand description

The single extension point of the pipeline: implement this trait once per destination language (C/cbindgen, JNI/Kotlin, Swift, Python, …) to teach the language-agnostic Registry how that language represents Rust types on the wire and what wrapper code to emit.

The trait has no language-specific concepts of its own, and — since the registry stopped asking it questions — one job left: per-item emission. The file emitter calls on_function / on_struct / on_enum / on_const to produce the per-item wrapper code, plus prerequisites and post_process_item around them and the two validate hooks for adapter invariants.

What used to be here and is not any more: which items to build, how composites decompose, and the wire form of each type. A generator states the first two into the builder (RegistryBuilder::export, RegistryBuilder::decompose) and answers the third by filling RegistryBuilder::crossings — so nothing in core calls back to ask. Moving emission out too is what would delete this trait entirely (prebindgen#251 phase E).

Anything language-specific the rest of the pipeline must carry — a JNI adapter’s Kotlin class names and exception info, a C adapter’s header names, etc. — rides in Self::Metadata, an opaque type the adapter chooses. It is set in each ConverterImpl::metadata, propagated by the resolver into TypeEntry::metadata, and read back by the adapter’s own emitter. Adapters that need no extras leave it at the default ().

§The rule an adapter must obey

“Classify off kind, spell off the syntax” tells an adapter where to get each fact. It is silent on the question adapters actually face — what the destination language ends up seeing. That one has its own answer:

Same kind ⇒ same destination-language type. The wire is the generator’s to choose, and may differ per spelling.

The weaker-sounding half is the important one. It is tempting to write “same kind ⇒ same wire”, and that is false — prebindgen’s own adapters violate it deliberately:

RustkindKotlin typewire
&[Payload]Ref(Slice)List<Payload>Long — a handle to a Rust-side Vec
Vec<Box<Payload>>Vec(Boxed)List<Payload>JObject — a Java List<Payload>

Two wires, one surface. Choosing a wire is exactly the generator’s job, and the destination-language wrapper absorbs the difference; a caller cannot tell. What a caller can tell — and what unwrapped exists to prevent — is the type changing because the source spelled a Box.

The rule scopes to converted positions: those where a converter stands between the Rust value and the destination and is therefore free to bridge. It cannot apply to a layout mirror, where the destination type is reinterpreted from the source struct’s bytes and is a layout fact rather than a surface choice — there Box<T> (a pointer) genuinely is a different destination type from T (inline), the spelling is load-bearing by construction, and no erasure can apply. The C adapter’s repr_c_struct is the one such position in-tree, and its own documentation carries that half.

Reusing a mirror’s spelling test in a converted position is how the rule gets broken (prebindgen#230, #292).

Required Associated Types§

Source

type Metadata: Clone + Default

Adapter-specific extras every resolved converter carries. The resolver copies this from each ConverterImpl it accepts into the matching TypeEntry, so emitter code reads metadata off the registry rather than through a parallel side channel.

Required Methods§

Source

fn on_function( &self, f: &Function, registry: &Registry<Self::Metadata>, emit: &Emit, ) -> TokenStream

Wrap a #[prebindgen] fn into the destination-language wrapper (e.g. JNI extern "C" fn).

Source

fn on_struct( &self, s: &Struct, registry: &Registry<Self::Metadata>, emit: &Emit, ) -> TokenStream

Per-struct emission. Typically empty for languages that get everything they need from auto-generated converters.

Source

fn on_variant( &self, v: &Variant, registry: &Registry<Self::Metadata>, emit: &Emit, ) -> TokenStream

Per-sum emission — an enum whose alternatives carry payloads.

Separate from Self::on_enum because the model separates them: the two are numbered differently and consumed as different constructs. An adapter with nothing to say about one shape returns an empty stream, as both in-tree adapters do for both.

Source

fn on_enum( &self, e: &Enum, registry: &Registry<Self::Metadata>, emit: &Emit, ) -> TokenStream

Per-enum emission — the fieldless shape, a named set of integers.

Provided Methods§

Source

fn prerequisites( &self, _registry: &Registry<Self::Metadata>, _emit: &Emit, ) -> Vec<Item>

Rust items the adapter’s emitted converters depend on (helper structs, type aliases, runtime-support code). Emitted at the top of the destination file, before all auto-generated converters.

Default: none. Wrapper adapters that compose a base adapter should forward to or extend the base’s prerequisites(). The resolved registry is supplied so prerequisites can be gated on what the (feature-aware) scan actually contains — e.g. emitting a per-opaque-handle item only for handles a scanned #[prebindgen] fn references.

Source

fn post_process_item( &self, _item: &mut Item, _registry: &Registry<Self::Metadata>, _emit: &Emit, )

Final post-processing pass applied to every emitted item right before write. Default: no-op.

Use this for cross-cutting transforms that would otherwise have to be remembered at every individual emit site — e.g. qualifying bare type references against a source module so the emitted converter bodies compile in the binding crate’s scope. Walks the entire AST, not just signatures, so type ascriptions and casts inside function bodies are covered.

Source

fn validate( &self, _binding: &Building<'_, Self::Metadata>, ) -> Result<(), String>

Adapter-invariant checks that need registry signatures — the earliest they can run (decl objects are built before any source is read). Called by RegistryBuilder::validate_with right after the declaration scan (so a missing fn has already hard-errored; validate sees only indexed items) and before plan application. An Err aborts the resolve as ScanError::AdapterInvariant with the message verbatim — e.g. jnigen rejects a .fun() member whose target has no receiver parameter of the class type.

Default: no checks.

Source

fn validate_resolved( &self, _registry: &Registry<Self::Metadata>, ) -> Result<(), String>

Post-resolve validation boundary — the counterpart of Self::validate that sees the fully resolved registry (converters, plans, metadata). Every artifact writer calls it before writing anything, so an invalid binding fails cleanly — with every problem reported at once — instead of panicking midway after a sibling artifact already reached disk. Deterministic over (self, registry); it runs once per write call, which keeps artifact writes order-independent.

Default: no checks.

Source

fn source_module(&self) -> Option<&Path>

Absolute path under which the source crate’s items are reachable from the generated file (e.g. zenoh_flat), for adapters that qualify emitted references against one. Drives the default Self::on_const: with a source module available, a named const re-emits as a path-alias to the source item instead of copying its initializer tokens. Default: None.

Source

fn on_const( &self, c: &Constant, _registry: &Registry<Self::Metadata>, emit: &Emit, ) -> TokenStream

Per-const emission. Default: a named const re-emits as a path-alias when Self::source_module is available — initializer tokens are never copied, so a const whose initializer references source-crate internals stays valid in the generated file. An adapter without a source module passes the const through verbatim.

A const reaching here is always named: prebindgen’s own injected feature checks are Guards, not consts, so this never has to recognise one.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§