Subsystem · IDL & codegen

IDL & codegen

One OMG IDL 4.2 front end lowers every .idl file into one model; code generation for 21 languages reads that model.

Learn

Learn — what is IDL?

OMG IDL — the Interface Definition Language — is a language-agnostic way to describe data types and remote interfaces. It first shipped in 1991 alongside CORBA. The current revision, IDL 4.2 (ISO/IEC 19516), drops legacy CORBA-only constructs and adds a clean set of annotations for modern middleware.

An IDL file looks roughly like a C header: it declares structs, unions, enums, interfaces and modules. The IDL compiler reads that file and generates language-native code — Rust types, C++ headers, Java classes, Python modules — that all encode and decode the same byte layout on the wire.

IDL is what makes DDS, CORBA, DDS-RPC and DDS-XRCE language-agnostic. Write the contract once, generate for every consumer. The wire format (XCDR1, XCDR2 for DDS; CDR for CORBA) is defined by the standard, not by the language.

@topic
@appendable
struct SensorReading {
    @key long sensor_id;
    double    value;
    @optional sequence<string> tags;
};
A minimal IDL fragment with XTypes annotations
Why

Why — why IDL matters

Three reasons IDL is non-negotiable for a serious middleware stack. First, language coverage: without a single source of truth, a multi-language deployment turns into N hand-written serializers, all subtly out of sync. With IDL, every binding renders the same type from the same source — and a CI step verifies that the renders stay aligned.

Second, wire-stable evolution: XTypes 1.3 annotations (@mutable, @final, @appendable, @optional) tell the codegen how a type may change over time. The annotation system means an old reader stays compatible with a new writer, and CI can flag breaking changes before they leave the merge request.

Third, vendor-lock-in avoidance: IDL is an OMG standard, not a vendor format. Move your IDL from RTI Connext, OpenSplice, Cyclone DDS or Fast-DDS into ZeroDDS without rewriting interfaces — only the codegen target changes.

ZeroDDS' own IDL parser is grammar-driven (not regex-based), with a vendor-extension pipeline that accepts dialect quirks from the four established vendors. That is the migration enabler: existing IDL files just compile.

What

What we built

The IDL stack: front end and model, the command-line tool, code generation, the runtime the generated Rust code calls, and the build.rs helper.

Seven back-ends and nine other toolchains read the same 420-case corpus. One IDL, every generator puts their output side by side — pick a case, pick the columns, read what each one wrote.

The front end parses IDL and lowers it into one model (zerodds-ast). zerodds-lang-rules maps that model onto the syntax tree of each target language, and zerodds-lang-render prints it. zerodds-idlc and zerodds-build run the same pipeline (zerodds-idl-compose): the command-line tool for any of the 21 languages, zerodds-build from a Cargo build.rs.

Crate Role Spec / status
idl-cst · idl-parse · idl-lower · ast Front end + model OMG IDL 4.2 (ISO/IEC 19516): lossless syntax tree (idl-cst), preprocessor, parser and vendor dialects (idl-parse), scopes, resolution and validation (idl-lower), the common model (ast). idl-compose puts the stages together.
idlc CLI compiler zerodds-idlc generate --lang <name> --out <dir> file.idl — one of 21 languages per run, or all of them with --lang all.
lang-rules · lang-cst · lang-render Code generation Rules per target language over the common model (lang-rules), the syntax trees of the 21 languages (lang-cst) and their printer (lang-render).
wire Rust runtime zerodds-wire: the XCDR1 and XCDR2 writers and readers the generated Rust code calls. The other languages have their runtime under runtime/<language> in the repository.
build build.rs helper zerodds-build runs the same pipeline from a Cargo build.rs: Config::new().include_dir("idl").compile(&["idl/types.idl"]). dds_type_impls(true) adds the DdsType impl that create_topic takes.

Coverage: zerodds-xcdr2-rust-1.0 →

Spec coverage — construct by construct

Every IDL 4.2 construct, where we implement it, how we test it. Each row links to a dedicated doc page, the source files on GitHub, and the corresponding test files. The status is the weakest one the IDL 4.2 coverage audit records for the construct's items.

Read the full OMG IDL 4.2 specification (PDF, omg.org) →

Construct Spec § Our doc Implementation Tests Status
Modules §7.4.1 module → idl-lower/src/lower.rs lowering.rs ✓ done
Constants §7.4.1.4.3 constant → idl-lower/src/expr.rs validation.rs ✓ done
Primitive types (short, long, float, double, char, …) §7.4.1.4.4 primitives → idl-lower/src/lower.rs lowering.rs ○ open
String, wstring §7.4.1.4.4.4 string → idl-parse/src/type_spec.rs validation.rs ✓ done
Sequence<T> §7.4.13.4.2.1 sequence → idl-parse/src/type_spec.rs validation.rs ✓ done
Array §7.4.5 array → idl-parse/src/decl.rs parse.rs ◐ partial
Struct §7.4.13.4.1 struct → idl-lower/src/lower.rs lowering.rs ◐ partial
Union §7.4.13.4.2 union → idl-lower/src/validate.rs validation.rs ✓ done
Enum §7.4.1.4.4.6 enum → idl-lower/src/lower.rs lowering.rs ✓ done
Bitset §7.4.13.4.6 bitset → idl-lower/src/complete.rs validation.rs ✓ done
Bitmask §7.4.13.4.7 bitmask → idl-lower/src/complete.rs validation.rs ○ open
Typedef §7.4.3 typedef → idl-lower/src/lower.rs idl_4_2_grammar.rs ✓ done
Forward declarations §7.4.6 forward decl → idl-lower/src/validate.rs recursion.rs ✓ done
Interface (CORBA / DDS-RPC) §7.4.10 interface → corba-codegen corba-codegen/tests ○ open
ValueType (CORBA legacy) §7.4.12 valuetype → corba-codegen corba-codegen/tests ◐ partial
Annotations (@key, @final, @mutable, @optional, …) §8 (Building Block) annotations → idl-lower/src/annotation.rs annotations.rs ◐ partial
Preprocessor (#include, #define, #if) §7.3 preprocessor → idl-parse/src/preprocess preprocess.rs ✓ done
How

How — using the IDL stack

From a .idl file to native types takes one command per target language. Build-system integration via build.rs (zerodds-build) and CMake (zerodds_idlc_generate).

Install

The idlc CLI ships as a separate crate. Install from crates.io or as a signed binary.

cargo install zerodds-idlc --version 1.0.0-rc.7

Generate code

Pick a target language with --lang and point zerodds-idlc at your .idl file. The output is one file per input, named after it; Java and Ada write fixed names.

zerodds-idlc generate --lang c          --out gen/c    types.idl
zerodds-idlc generate --lang c++        --out gen/cpp  types.idl
zerodds-idlc generate --lang rust       --out gen/rust types.idl
zerodds-idlc generate --lang typescript --out gen/ts   types.idl
zerodds-idlc generate --lang 'c#'       --out gen/cs   types.idl
zerodds-idlc generate --lang java       --out gen/java types.idl
zerodds-idlc generate --lang python     --out gen/py   types.idl

Build-system integration

For Cargo, zerodds-build runs the same pipeline from a build.rs. The example below regenerates types.rs whenever types.idl or a file it includes changes. For CMake, zerodds_idlc_generate() runs zerodds-idlc generate with --stamp and --depfile.

// build.rs
fn main() -> Result<(), zerodds_build::Error> {
    zerodds_build::Config::new()
        .include_dir("idl")
        .dds_type_impls(true)
        .compile(&["idl/types.idl"])
}

Runnable: idlc-buildrs compiles an .idl through this build script and round-trips a sample.

Lint your IDL

zerodds-idlc lint first reports front-end diagnostics, then checks valid IDL for choices such as unbounded collections, a @topic without a @key, and framing cost in wide @mutable structures. These are model warnings, not a comparison of old and new schemas or a runtime QoS compatibility test. zerodds-lint is a separate internal Rust workspace tool.

zerodds-idlc check types.idl
zerodds-idlc lint types.idl

Annotations cheatsheet

XTypes 1.3 annotations control wire-compatible evolution. Pick the right annotation up front — changing it later is a breaking change.

Annotation Use
@topic @topic — mark a struct as a top-level DDS topic type. CI checks topic-name uniqueness.
@key @key — designate fields as the keyset for instance handling. Must be invariant across the lifetime of an instance.
@appendable @appendable — default. Fields may be added at the end without breaking older readers.
@mutable @mutable — fields may be added or removed by name. Slightly larger wire size; more flexibility.
@final @final — frozen. Adding a field is a breaking change. Use for high-rate, locked-in types.
@optional @optional — field may be absent on the wire. Readers see Option<T>.

Per-language pointers

Rust

zerodds-idlc generate --lang rust --out <dir> types.idl, or from build.rs with zerodds-build on every cargo build. The generated code calls zerodds-wire; dds_type_impls(true) adds the DdsType impl. Async via dcps-async.

C++

zerodds-idlc generate --lang c++ --out <dir> types.idl writes types.hpp; --cpp-mapping selects psm (default, DDS-PSM-Cxx 1.0), native or classic. In CMake, zerodds_idlc_generate() runs it before compilation.

C# / .NET

zerodds-idlc generate --lang c# --out <dir> types.idl writes types.cs, and types.TypeSupport.cs for eligible topic types. Add them to your MSBuild project; the runtime packages target .NET 8.

Java

zerodds-idlc generate --lang java --out <dir> types.idl writes ZdTypes.java and ZdCodec.java, and ZdTypeSupports.java for eligible topic types. --java-mapping selects psm (default, DDS-Java-PSM 1.0 clause 9) or omg. Pure Java — no JNI.

Python

zerodds-idlc generate --lang python --out <dir> types.idl writes one module per input with the types, their codecs and ZD_TOPIC_DESCRIPTORS. It needs the zerodds_wire runtime (PyPI zerodds-wire); zerodds.IdlTopic takes a descriptor from that table.

TypeScript

zerodds-idlc generate --lang typescript --out <dir> types.idl emits a TypeScript module per input. Pair with ts-node for the Node.js bundle or ts-wasm for the browser bundle (WebSocket-only).

C

zerodds-idlc generate --lang c --out <dir> types.idl writes a C header with your IDL types and their codec; it includes zerodds_wire.h from runtime/c. Independent from the DCPS runtime: combine with the zerodds-c-api FFI library (-lzerodds) for full DCPS access from C.