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|--helpDisplay Help.
-qBe quiet.
-vVerbose 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:
&Classfor a constant parameter,&mut Classfor a mutable one.For a
methodfunction, the controlling first parameter becomes theselfreceiver 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 |
|---|---|
|
|
character |
|
signed int 8 |
|
signed int 16 |
|
signed int 32 |
|
signed int 64 |
|
unsigned int 8 |
|
unsigned int 16 |
|
unsigned int 32 |
|
unsigned int 64 |
|
float 32 |
|
float 64 |
|
|
|
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), andnew_1,new_2… for further overloads, since Rust has no overloading.method-role functions become&selfmethods (or&mut selfwhen the controlling parameter is mutable); the controlling object becomes the receiver.For a scalar field, the getter returns a copy, while a companion
_mutaccessor hands out a real&mutreference 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 Dropcalling 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 animpl 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
STATICownership — and any class returned by reference — is wrapped instd::mem::ManuallyDropso dropping it never frees the underlying object (a non-owning view into the object). Otherwise an owned wrapper is returned, which runs itsDrop(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 (withoutlibprefix 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).