Skip to content

Imported Units

#include "file.idl" copies a file into the current unit, so both files share one scope and one output. import names another compilation unit: the compiler generates that unit on its own and the importing unit refers to its types.

import "timestamp"; // imports timestamp/index.idl
import "timestamp/util.idl"; // imports one file of that package
import "timestamp@v1"; // binds the default API version of the unit

Paths resolve against the dependency mounts of xidl.toml first and against the importing file’s directory afterwards. A directory resolves to the index.idl inside it, so a package is imported by its name. The reference prefix is the last path segment without its .idl suffix: timestamp for timestamp/index.idl, util for timestamp/util.idl.

Every unit is compiled and generated by itself:

  • app.idl becomes app.rs.
  • timestamp/index.idl becomes timestamp.rs and holds every version of the unit.

A unit refers to another unit through its prefix and an API version:

app.idl
import "timestamp";
struct Event {
timestamp::v1::Instant at;
};
app.rs
use crate::timestamp;
pub struct Event {
pub at: timestamp::v1::Instant,
}

The version in the reference selects a module of the imported unit:

timestamp.rs
pub mod v1 {
pub struct Instant { pub at: String }
}
pub mod v2 {
pub struct Instant { pub at: String, pub monotonic: Option<String> }
}

Generating a unit generates every unit it imports, transitively and once each. A cycle between units is an error, and so is a path that resolves to a directory without an index.idl.

#include "x.idl" import "x"
Copies the file text yes no
Shares one output yes no
Generated separately no yes
Reference form Pet x::v1::Pet
Version selector copies that projection binds the default version

A version selector picks one version of the named unit. import "timestamp@v1" binds it, so timestamp::Instant means timestamp::v1::Instant:

import "timestamp@v1";
struct Event {
timestamp::Instant at;
};

#include "types.idl@v1" copies only the projection of types.idl at v1. The projection keeps the definitions that version declares and wraps them in a module named after the version, so references read v1::Instant:

#include "types.idl@v1"
struct Event {
v1::Instant at;
};

Every reference into an imported unit is checked against that unit. A reference that names an unknown unit, an unknown version, or a declaration the version does not have fails with a diagnostic naming the reference and the unit. Naming a version is required for a versioned unit, and naming one for an unversioned unit is an error.

Every target reaches an imported unit the way its language does:

  • rust, rust-axum, and rust-jsonrpc emit use crate::<unit>;.
  • typescript and typescript-rest re-export the unit under its prefix.
  • go and go-rest mirror the unit paths: a unit at pkg/timestamp.idl becomes pkg/timestamp.go with package name pkg, and a file that references it imports that package and qualifies the reference.
  • openapi and openrpc emit a $ref to the document the unit is generated as, such as openapi_timestamp.v1.json#/components/schemas/v1.Instant.