Subsystem · IDL & codegen

IDL & codegen

One OMG IDL 4.2 grammar drives type generation for seven languages. Same .idl file, same wire bytes, idiomatic native types in every binding.

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

Eight crates make up the IDL stack. Click into any crate name for its spec-coverage report.

At the centre is the parser. Around it sit seven codegen back-ends — one per target language — plus a CLI (idlc) that orchestrates them. The parser produces a typed AST; each codegen back-end is a small visitor that emits language-native source.

Crate Role Spec / status
idl Parser + AST OMG IDL 4.2 (ISO/IEC 19516) — grammar-driven, vendor-extension pipeline for RTI / OpenSplice / Cyclone / Fast-DDS dialects.
idlc CLI compiler zerodds-idlc --<lang> -o <dir> file.idl — orchestrates parser + one of the seven codegen back-ends.
idl-rust Rust codegen Native Rust types with DdsType-Trait, #[derive] macros, async-friendly. Targets stable Rust 1.88+.
idl-cpp C++17 codegen DDS-PSM-Cxx 1.0 compliant headers. ABI-stable consumption, namespaces match IDL modules.
idl-csharp C# / .NET codegenIdiomatic C# with records / async-await, NuGet-packageable, .NET 8 + netstandard2.1 targets.
idl-java Java codegen OMG DDS-Java-PSM (Pure Java, no JNI). Records, sealed types, virtual threads ready.
py Python decorator Codegen-frei: @idl_struct-Decorator wraps eine @dataclass + Field-Type-Marker; Encoder/Decoder entsteht zur Decorator-Zeit. Byte-identisch zum Rust-Codegen-Pfad.
idl-ts TypeScript codegenTwo flavours: Node.js bundle and a browser bundle (WebSocket-only). Strict TypeScript, no any.
idl-cC99 codegen POD structs + XCDR2 encoder/decoder tables via zerodds-idl-cpp::c_mode. Vendor spec zerodds-xcdr2-c-1.0. Combine with zerodds-c-api (-lzerodds) for the DCPS runtime.
cdr / cdr-deriveCDR encoders cdr + cdr-derive: CDR / XCDR1 / XCDR2 encoders shared by every codegen target. Byte-identical to Cyclone capture replays in CI.

Vendor spec: zerodds-idl-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.

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

Construct Spec § Our doc Implementation Tests Status
Modules §7.4.1 module → idl/src/ast parse_omg.rs ✓ RC3
Constants §7.4.1.4.3 constant → idl/src/ast parse_omg.rs ✓ RC3
Primitive types (short, long, float, double, char, …) §7.4.1.4.4 primitives → idl/src/ast parse_omg.rs ✓ RC3
String, wstring §7.4.1.4.4.4 string → idl/src/ast parse_omg.rs ✓ RC3
Sequence<T> §7.4.13.4.2.1 sequence → idl-rust/src roundtrip.rs ✓ RC3
Array §7.4.5 array → idl-rust/src roundtrip.rs ✓ RC3
Struct §7.4.13.4.1 struct → struct_emit.rs roundtrip.rs ✓ RC3
Union §7.4.13.4.2 union → idl-rust/src union_validation.rs ✓ RC3
Enum §7.4.1.4.4.6 enum → enum_emit.rs parse_omg.rs ✓ RC3
Bitset §7.4.13.4.6 bitset → bitset_emit.rs bitfield_validation.rs ✓ RC3
Bitmask §7.4.13.4.7 bitmask → idl-rust/src bitfield_validation.rs ✓ RC3
Typedef §7.4.3 typedef → typedef_emit.rs anon_types.rs ✓ RC3
Forward declarations §7.4.6 forward decl → idl/src/ast forward_decl_completion.rs ✓ RC3
Interface (CORBA / DDS-RPC) §7.4.10 interface → corba-codegen corba-codegen/tests ✓ RC3
ValueType (CORBA legacy) §7.4.12 valuetype → corba-codegen corba-codegen/tests ✓ RC3
Annotations (@key, @final, @mutable, @optional, …) §8 (Building Block) annotations → annotations.rs annotations.rs ✓ RC3
Preprocessor (#include, #define, #if) §7.3 preprocessor → idl/src/preprocessor preprocessor.rs ✓ RC3
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 / CMake / Gradle / setup.py.

Install

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

cargo install zerodds-idlc

Generate code

Pick a target language and point idlc at your .idl file. The output is one file per top-level module.

zerodds-idlc --c      -o gen/c      types.idl
zerodds-idlc --cpp    -o gen/cpp    types.idl
zerodds-idlc --rust   -o gen/rust   types.idl
zerodds-idlc --ts     -o gen/ts     types.idl
zerodds-idlc --csharp -o gen/cs     types.idl
zerodds-idlc --java   -o gen/java   types.idl
zerodds-idlc --python -o gen/py     types.idl

Build-system integration

Every binding ships a thin build helper. The Rust example below regenerates types.rs whenever types.idl changes.

// build.rs
fn main() {
    zerodds_idlc::build()
        .file("types.idl")
        .out_dir(env!("OUT_DIR"))
        .generate()
        .expect("idl codegen");
}

Lint your IDL

dds-lint reads .idl files and flags common issues: missing @key annotations, types that break wire compatibility, accidental @mutable on hot-path messages. Use it as a pre-commit hook or CI step.

dds-lint check 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

cargo add zerodds-dcps zerodds-idlc. Use build.rs to regenerate types.rs on every cargo build. Async via dcps-async.

C++

zerodds-idlc --cpp emits C++17 headers under your include path. Configure CMake to call zerodds-idlc before compilation.

C# / .NET

zerodds-idlc --csharp emits a .cs file per module. Add it to your MSBuild project. Target .NET 8+ or netstandard2.1.

Java

zerodds-idlc --java emits a .java file per IDL declaration under the standard Maven layout (one file per package). Pure Java — no JNI.

Python

zerodds-idlc --python emits one Python module per IDL module with @idl_struct decorators and type hints. Same wire bytes as the Rust path.

TypeScript

zerodds-idlc --ts emits a TypeScript module. Pair with ts-node for the Node.js bundle or ts-wasm for the browser bundle (WebSocket-only). Strict TypeScript.

C

zerodds-idlc --c emits a plain C99 header with POD structs and XCDR2 typesupport tables for your IDL types — wire-compatible with every other ZeroDDS codegen target. Independent from the runtime: combine with the zerodds-c-api FFI library (-lzerodds) for full DCPS access from C.