Proxy2Rust

Consumes the proxy IR and generates idiomatic Rust bindings, so that Rust code can call into the bound library across the C ABI.

Warning

The Rust backend is experimental. It covers a subset of the proxy IR and several constructs are not yet supported (see Limitations chapter). Generated crates and the conventions described here are subject to change.

Using the tool

The Proxy2rust tool is invoked as a subcommand of the GNATpolyglot program. It must be provided with at least a proxy file and an output path:

$> gnatpolyglot proxy2rust <proxy-file> -o<output-path> {<switch>}
  • -h|--help

    Display Help.

  • -q

    Be quiet.

  • -v

    Verbose output.

  • -o|--output=<outputPath>

    Path to the directory in which the Rust interface should be generated. Directories along the path are created if they do not exist yet.

  • --header-file=<file>

    Prepend the content of the given file to all sources generated by GNATpolyglot.

Modules

Modules in the JSON Proxy become Rust modules (mod), declared in src/lib.rs and emitted as src/<module>.rs. Nested modules become nested modules. Module, function and field names are lowercased to snake_case; class and enum names use PascalCase. Identifiers that collide with a Rust keyword are escaped with the raw-identifier syntax (r#priv, r#type …).

Functions

Function placement

Free functions of a module become module-level pub fn items. Functions carrying a method role instead become inherent methods of the role’s class (&self / &mut self — see Classes), and constructors follow the naming described there. Function names are lowercased to snake_case.

Because Rust has no overloading, functions that would otherwise share a name within the same scope (a module’s free functions, or one class’s methods) are disambiguated with a numeric suffix: the first keeps the base name, the rest become ..._1, ..._2

Parameters and return values

  • Scalars are passed and returned by value.

  • Class-typed parameters are passed by reference: &Class for a constant parameter, &mut Class for a mutable one.

  • For a method function, the controlling first parameter becomes the self receiver and is dropped from the argument list.

  • Returned class values follow the ownership rules described under Ownership.

Types

Scalars

Scalars are mapped one-to-one to fixed-size Rust primitives:

Proxy type

Rust type

bool

bool

character

u8

signed int 8

i8

signed int 16

i16

signed int 32

i32

signed int 64

i64

unsigned int 8

u8

unsigned int 16

u16

unsigned int 32

u32

unsigned int 64

u64

float 32

f32

float 64

f64

void

()

128-bit scalars are not supported (see Limitations).

Enumerations

An enumeration in the proxy becomes a #[repr(...)] Rust enum, sized to the underlying proxy type and deriving the usual value traits. The representation values carried by the proxy are preserved.

A proxy enumeration Color with enumerators Red, Green and Blue generates:

#[repr(i8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Color {
    Red = 0,
    Green = 1,
    Blue = 2,
}

Classes

A proxy class maps to a newtype wrapping the opaque pointer to the internal object:

#[repr(transparent)]
pub struct Value(pub(crate) std::ptr::NonNull<std::ffi::c_void>);

The pointer field is pub(crate) so wrappers can be built and unwrapped across the generated crate’s modules while staying hidden externally.

Constructors and methods are emitted as inherent functions:

  • Constructors are named new (the first one), new_default (the zero-parameter constructor), and new_1, new_2 … for further overloads, since Rust has no overloading.

  • method-role functions become &self methods (or &mut self when the controlling parameter is mutable); the controlling object becomes the receiver.

  • For a scalar field, the getter returns a copy, while a companion _mut accessor hands out a real &mut reference into the object.

use test::test as pkg;

let mut v = pkg::Value::new(10);
println!("v = {}", v.get_v());   // copy
*v.get_v_mut() = 20;             // mutate in place
println!("v = {}", v.get_v());

Ownership

Whether a wrapper frees its underlying object is governed by two things:

  • Per type. A class that has a destructor gets an impl Drop calling the free function exposed by the proxy, so an owned value frees the object at end of scope. A class that has a copy function gets an impl Clone.

  • Per returned value. When a value is returned, the proxy’s ownership annotation decides whether the returned wrapper owns the object. A value returned with STATIC ownership — and any class returned by reference — is wrapped in std::mem::ManuallyDrop so dropping it never frees the underlying object (a non-owning view into the object). Otherwise an owned wrapper is returned, which runs its Drop (if any) at end of scope.

Inheritance

A type that derives from a (non-virtual) parent gets Deref/DerefMut implementations that transparently coerce to the parent type, so a &Child can be passed wherever a &Root is expected for class-wide dispatch.

use test::test as pkg;

let root = pkg::Root::new(1, 2);
let child = pkg::Child::new(3, 4, 5);

// Deref coercion: &Child coerces to &Root
pkg::p_root(&root);
pkg::p_root(&child);

Crate layout

Generation produces a complete Cargo crate:

$> gnatpolyglot proxy2rust 2proxy/proxy.json -o 2rust
$> find 2rust
2rust/
2rust/Cargo.toml          # package metadata (name = <proxy name>, edition 2021)
2rust/build.rs            # links the native proxy library
2rust/src
2rust/src/lib.rs          # `pub mod` declaration per module
2rust/src/<module>.rs     # one file per module: enums, structs, impls, free functions

Each generated module file contains a private ffi submodule holding the extern "C" declarations of the proxy symbols; the safe API wraps those calls.

Building

build.rs reads two environment variables to locate the statically built, encapsulated proxy library, and emits the right link directives:

  • POLYGLOT_PROXY_LIB_DIR — directory containing the static archive.

  • POLYGLOT_PROXY_LIB_NAME — name of the archive (without lib prefix or extension).

It also links the platform’s required system libraries (dl and pthread on Linux; advapi32 on Windows, which the GNAT runtime needs).

$> POLYGLOT_PROXY_LIB_DIR=<proxy-lib-dir> \
   POLYGLOT_PROXY_LIB_NAME=<proxy-lib-name> \
   cargo build --manifest-path 2rust/Cargo.toml

Supported platforms

Proxy2Rust currently supports only 64-bit Linux and Windows platforms (see Limitations).