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 — 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;
};
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 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) →
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.