Skip to main content

Crate ridl_rt

Crate ridl_rt 

Source
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 thisThe generated face gives youOver this port
Read a signalClient::<name>port::SignalReader, returning sample::Sample
Publish a signalPublisher::<name>, commitport::SignalWriter
Receive an eventClient::subscribe_<name>, Client::next_eventport::EventSource
Raise an eventPublisher::<name>port::EventSink
Call a command or a queryClient::<name>, returning the call’s futureport::Caller
Serve a command or a queryProvider, driven by the generated serveport::Handler
Read a provisioned constantnothing yet — call the portport::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.