Expand description
The library that generated code links and a runtime implements.
ridl-rt defines, once, the vocabulary that a package generated from ridl
and a runtime agree on: identity, time and the envelope, samples, the
payload traits, the interaction descriptors, the ports, and the contract and
transport errors. It contains no runtime, but it carries the pure data
structures every runtime would otherwise write alone, such as the
correlate call table. A runtime is a separate crate that
implements the traits of the port module (ADR-0020 decision 6), and
generated code calls those traits without naming the runtime.
With the std feature off — the default — the crate is no_std and
allocates nothing; it contains no unsafe code and has no dependency in any
feature combination. The cargo features flatbuffers,
proto3 and repr-c name the payload encodings. flatbuffers enables the
flatbuffers module, the reading and writing a generated
Payload<FlatBuffers> implementation shares; proto3 and repr-c enable
nothing in this version. A fourth feature, std, off by default, is not an
encoding: it links the standard library and enables the task module —
block_on, which waits on a future by parking the thread until a deadline,
noop_waker, and flag_waker, whose wake sets a flag the caller reads —
for a blocking client built over an async one and for a frame loop that
polls a future once per frame, or again in the same frame when the future
woke itself. task links the standard library and allocates — one Arc per
call of any of the three functions. trace also links the standard
library, for its hook: a OnceLock and an Error impl, with no allocation.
Every other module stays no_std with the feature on.
Every public item lives in one of nine modules, or in one of the two that
the flatbuffers and std features add. Generated code names each
item by its full path, for example ridl_rt::sample::Sample, and imports
none, because several names here — Duration, Handler, Kind — are also
names in core or in application code. The eighth module, face, is the
one whose items generated code implements rather than calls: the traits
that carry the fixed methods of a generated Client and Publisher —
new, next_event, commit, and under std with_timeout and
set_timeout — so that a member of an interface may carry one of those
names. A consumer of a generated face brings them into scope with
use <crate>::<iface>::prelude::*; for each interface whose face it uses;
each prelude brings the traits that interface’s types implement, and
rustc reports a prelude as an unused import when the other imported
preludes already bring every item it would add.
The ninth module, trace, holds TraceContext, the optional trace context
that a call or an event carries across a port. With the std feature it also
holds Propagation, AlreadySet, set_propagation and propagation, the
application’s hook for that context.
§Where to start
Most of this crate is called by generated code, not by an application. An
application implements one generated Provider trait and calls generated
Client and Publisher methods; those methods call the ports here. So the
shortest path in is to read one interaction kind at a time, from the
generated side:
| To do this | The generated face gives you | Over this port |
|---|---|---|
| Read a signal | Client::<name> | port::SignalReader, returning sample::Sample |
| Publish a signal | Publisher::<name>, commit | port::SignalWriter |
| Receive an event | Client::subscribe_<name>, Client::next_event | port::EventSource |
| Raise an event | Publisher::<name> | port::EventSink |
| Call a command or a query | Client::<name>, returning the call’s future | port::Caller |
| Serve a command or a query | Provider, driven by the generated serve | port::Handler |
| Read a provisioned constant | nothing yet — call the port | port::FixedReader |
docs/technotes/ridl-rt-by-example.md in this repository walks that table
from top to bottom against concrete generated code, introducing each type
at the point where the generated code first needs it. The library’s own
as-built description is docs/design/ridl-rt.md.
Two properties hold everywhere and are worth knowing before reading any
individual item. No port method waits — every one returns immediately,
and a call’s outcome is retrieved separately through a
port::Correlation. A face that waits registers its interest with the
port::Wakeable extension, keyed by a port::Interest, and reads the
port again when the runtime wakes it; a runtime that serves a generated
async client implements that extension. And no port names a payload
type — ports carry interface numbers, ordinals and bytes, and the
generated binding is what encodes and decodes, through payload::Ref.
A call made through the generated async client returns a named future,
and the runtime keeps the call’s outcome until the caller releases it. The
future releases it: it calls port::Caller::forget when it leaves the
waiting phase — in the poll that takes the outcome, at the call’s deadline,
and on drop while the call is still waiting — so a program over the
generated face holds one slot per call in flight and forgets nothing by
hand. A program that sends through port::Caller directly must call
port::Caller::forget on a correlation once it has read the outcome;
otherwise a runtime that bounds how many calls it holds at once refuses
every call with port::SendError::Busy once that bound is reached.
§How a runtime presents its ports
A runtime crate exposes one handle type per port role it implements — a
port role is one port trait — rather than one type implementing them all,
and it may also offer an aggregate handle covering the port set one
interface’s face needs. A generated face is built over one value
implementing at least the port traits its interface needs: the role handle
itself when the face needs exactly one, and an aggregate — the runtime’s,
or one the application writes over role handles — when it needs more,
because a generated Client may be bound over SignalReader,
EventSource and Caller at once. Either value reaches the face by value
or as a &mut borrow of itself, because every port trait is implemented
for &mut P and the &self-only traits also for &P. In a runtime whose
handles are used from more than one thread, a handle whose port traits all
take &self is Send + Sync, because several threads may read one store
at once, and a handle carrying a trait with a &mut self method is Send
and need not be Sync, because one thread drives each. No port trait here
carries Send or Sync as a supertrait, so a single-threaded no_std
runtime whose handles use Cell or RefCell internally is held to
neither; a runtime checks its own handles itself, with a compile-time
assertion. ADR-0021 decision 12 records this.
Modules§
- contract
- Identity, and the interaction descriptors generated code writes.
- correlate
- The caller-side call table and the waiter registry a runtime keeps behind
Wakeable(ADR-0021 decision 15). - encoding
- The payload encodings (ADR-0020 decisions 1 and 5).
- error
- The error strata of ridl §10 that a runtime reports, and the two errors a generated face returns.
- face
- The traits a generated face implements: the fixed methods of the consumer and provider faces the Rust backend emits (ADR-0023 decision 7, ADR-0021 decision 19).
- flatbuffers
- Shared reading and writing for the FlatBuffers payload encoding.
- payload
- Encoding, verifying and decoding a payload.
- port
- The ports: the traits a runtime implements and generated code calls.
- sample
- Time, the envelope, and the values a read returns (ridl §3.1, §4.5, §9),
with the two computations a consuming runtime makes on an envelope: a
value’s freshness (
Freshness::of) and event loss (EventSeqTracker). - task
- Waiting on a future from a thread, a waker that does nothing, and a waker that sets a flag.
- trace
- The trace context a call or an event can carry.