nros/lib.rs
1//! # nros
2//!
3//! A lightweight ROS 2 client library for embedded systems.
4//!
5//! This crate provides a unified API for building ROS 2 nodes in Rust,
6//! with support for `no_std` environments and embedded targets.
7//!
8//! ## Features
9//!
10//! - **no_std compatible**: Works on bare-metal and RTOS targets
11//! - **Zero-copy where possible**: Minimizes memory allocations
12//! - **Type-safe**: Compile-time verification of message types
13//! - **ROS 2 compatible**: Interoperates with standard ROS 2 nodes via rmw_zenoh
14//!
15//! ## Quick Start
16//!
17//! ```ignore
18//! use nros::prelude::*;
19//! use std_msgs::msg::Int32;
20//!
21//! let config = ExecutorConfig::from_env().node_name("my_node");
22//! let mut executor = Executor::open(&config)?;
23//!
24//! let node = executor.node_builder("my_node").build()?;
25//! let publisher = executor.node_mut(node).create_publisher::<Int32>("/my_topic")?;
26//! publisher.publish(&Int32 { data: 42 })?;
27//!
28//! executor.node_mut(node).create_subscription::<Int32, _>("/topic", |msg: &Int32| {
29//! println!("Received: {}", msg.data);
30//! })?;
31//!
32//! executor.spin(SpinOptions::default());
33//! ```
34//!
35//! ## Executor Sizing
36//!
37//! The executor's static memory layout is controlled via environment variables
38//! at build time:
39//!
40//! - **`NROS_EXECUTOR_MAX_CBS`** (default 4) — maximum number of registered
41//! callbacks (subscriptions + timers + services + guard conditions).
42//! - **`NROS_EXECUTOR_ARENA_SIZE`** (default 4096) — byte budget for storing
43//! callback closures inline.
44//!
45//! A subscription's arena receive buffer is sized from the MESSAGE TYPE's own
46//! serialized bound (phase-392 W3c), not from that knob, wherever the bound is
47//! reachable. `.rx_buffer::<N>()` is the opt-out: it says "give me exactly `N`
48//! bytes per slot" and skips the derivation, which is what a peer that pads or
49//! a `.msg` bound looser than the traffic needs (e.g.
50//! `node_mut(id).subscription(t).typed::<M>().rx_buffer::<4096>().build(cb)`).
51//!
52//! ## Transport Backends
53//!
54//! Phase 248 C5c — `nros` is RMW- and platform-AGNOSTIC. It carries only the
55//! `rmw-cffi` vtable; the concrete backend (zenoh / xrce / cyclonedds) enters
56//! the link graph via the board crate (embedded), the board-less app's own
57//! `nros-rmw-*` dep (native), or the `nros-c`/`nros-cpp` staticlib root (D3),
58//! and self-registers — before `main` on a hosted target (an `.init_array`
59//! ctor, phase-249 P4b.1; the ~~`RMW_INIT_ENTRIES`~~ walker at `Executor::open`
60//! before that), or through the board's explicit `<backend>::register()`.
61//! The concrete session type is resolved automatically; advanced users can
62//! access it via `nros::internals::RmwSession`.
63//!
64//! ## Crate Features
65//!
66//! `nros` exposes only FUNCTIONAL features — `std`/`alloc`, the `rmw-cffi`
67//! vtable, `lending`, `bridge`/`config`, `param-services`,
68//! `lifecycle-services`, `safety-e2e`, `stream`, `ffi-sync`, and the ROS
69//! edition (`ros-humble`/`ros-iron`). There are NO `platform-*` or concrete
70//! `rmw-*` selector features (Phase 248 C7). Platform + RMW are selected by the
71//! board / staticlib root via dependencies, not `nros` features. The
72//! `zephyr_component_main!` entry macro is gated only on `rmw-cffi` (it's
73//! framework entry codegen, like `nros::main!`), not a platform feature.
74//!
75//! **ROS edition** (select one; RFC-0056 — the per-distro interop profile):
76//! - `ros-humble` - ROS 2 Humble (default; `TypeHashNotSupported`, XCDR1)
77//! - `ros-iron` - ROS 2 Iron (RIHS01 type hash)
78//! - `ros-jazzy` - ROS 2 Jazzy (RIHS01; XCDR2/appendable is phase-303)
79//!
80//! **Other**:
81//! - `std` (default) - Enable standard library support
82//! - `alloc` - Enable heap allocation without full std
83//!
84//! ## Further Reading
85//!
86//! - [`guide`] — tutorials: getting started, services, configuration,
87//! ROS 2 interop, and troubleshooting
88//! - [Message Generation](https://github.com/jerry73204/nano-ros/blob/main/docs/guides/message-generation.md)
89//! — codegen reference (all options, output structure, bundled interfaces)
90//! - [Environment Variables](https://github.com/jerry73204/nano-ros/blob/main/docs/reference/environment-variables.md)
91//! — complete buffer tuning reference
92//! - [ROS 2 Interop](https://github.com/jerry73204/nano-ros/blob/main/docs/reference/rmw_zenoh_interop.md)
93//! — protocol details (key expressions, liveliness, attachments)
94//! - [Examples](https://github.com/jerry73204/nano-ros/tree/main/examples)
95//! — working examples by platform (native, QEMU, ESP32, Zephyr)
96
97#![no_std]
98
99// ── Feature validation (mutual exclusivity) ─────────────────────────────
100// Phase 248 C5c/C7 — `nros` carries NO `platform-*` selector features, so the
101// platform mutual-exclusion `compile_error!` is gone. The platform is selected
102// by the board / staticlib root via an `nros-platform` dep, and nros-node picks
103// the kernel primitive at runtime (C2 wake-probe).
104// Only `rmw-cffi` is exposed at this layer; the cffi shim selects the
105// concrete backend at the C ABI level from its registry (filled by each
106// backend's self-registration; the ~~`RMW_INIT_ENTRIES`~~ walker once).
107
108// At most one ROS edition (RFC-0056 — the axis is compile-time exclusive).
109#[cfg(any(
110 all(
111 feature = "ros-humble",
112 any(feature = "ros-iron", feature = "ros-jazzy")
113 ),
114 all(feature = "ros-iron", feature = "ros-jazzy"),
115))]
116compile_error!("`ros-{humble,iron,jazzy}` are mutually exclusive — select one ROS edition.");
117
118// phase-359 W10 stage B — no `std` feature. The hosted edge (`env`) reads
119// through the C library; only the test harness links `std`.
120#[cfg(test)]
121extern crate std;
122
123#[cfg(feature = "alloc")]
124extern crate alloc;
125
126// Phase 216.A.5 — the `nros::node!()` proc-macro emits absolute paths
127// under `::nros::*` (so downstream Node pkgs only need a single `nros`
128// dep). For the in-crate macro-expansion test in `node.rs`, alias the
129// `nros` crate name to itself so those absolute paths resolve. Gated on
130// `cfg(test)` to keep the alias out of normal builds.
131#[cfg(test)]
132extern crate self as nros;
133
134// Phase 248 C5c — the umbrella's force-link statics
135// (`__FORCE_LINK_{PLATFORM_CFFI,ZENOH,XRCE,CYCLONEDDS_SYS}`) are REMOVED along
136// with `nros`'s concrete-backend deps. `nros` no longer references any concrete
137// RMW or platform crate, so it has nothing to force-link. Registration + the
138// `nros_platform_*` link anchor now live with whoever owns the concrete crate:
139// * embedded — the BOARD crate force-links its backend + calls
140// `<backend>::register()` in its boot path (C5a);
141// * board-less native — the APP owns `nros-rmw-*` + a `#[used]` force-link in
142// its `main.rs`, and `nros-platform-cffi[posix-c-port]` anchors the C symbols;
143// * C/C++ staticlib — `nros-c`/`nros-cpp` bundle one backend (D3) and anchor
144// `nros-platform` themselves.
145
146// Phase 249 P1 — `__register_linked_rmw()` (a Phase 248 C5c no-op kept only so the
147// `nros::main!` framework's call sites compiled) is REMOVED along with those call
148// sites. Backend registration never routed through the backend-agnostic `nros` crate:
149// hosted auto-registers before `main` (an `.init_array` ctor, phase-249 P4b.1; the
150// ~~`RMW_INIT_ENTRIES`~~ walk at `Executor::open` before that); embedded
151// boards perform the explicit `<backend>::register()` in their boot path (C5a). One
152// Rust trigger = the board/app explicit register (phase-249).
153
154// phase-391 W5 — build-time knobs (`MAX_COMPONENTS`, `COMPONENT_SLOT_BYTES`).
155// Ungated: they are plain consts, useful to size caller-supplied storage
156// whether or not the runtime module that consumes them is compiled in.
157pub mod config;
158
159// phase-391 W5 — caller-supplied component-pool storage sizing. Ungated for the
160// same reason `config` is: plain arithmetic, useful to size a `static` whether
161// or not `node_runtime` is compiled in.
162// Issue 1419 / issue 1556 item 1 — the census hooks every language's node API
163// calls (unconditionally; the bodies exist only under `metadata-mode`).
164#[doc(hidden)]
165pub mod census_hooks;
166pub mod dispatch_tag;
167pub mod guide;
168#[cfg(feature = "metadata-mode")]
169pub mod metadata_mode;
170/// phase-463 W7 — the host profile (`nros_node::executor::profile`), for the
171/// hosted edges that read `$NROS_PROFILE_OUT` (`nros-cpp`, `nros-board-linux`).
172#[cfg(all(feature = "profile-mode", feature = "rmw-cffi"))]
173pub use nros_node::executor::profile;
174/// phase-509 P10 (RFC-0108) -- the Rust tier runners' level core: one
175/// executor per core, a worker per level.
176#[cfg(all(feature = "alloc", feature = "rmw-cffi"))]
177pub mod levels;
178pub mod node;
179pub mod node_metadata;
180/// Phase 212.M.5.a.2 — executor-backed component runtime.
181///
182/// Binds [`Node`] / [`ExecutableNode`] to a live
183/// [`Executor`] so a Node pkg can actually run (versus
184/// [`MetadataRecorder`] which
185/// is the planner-side metadata sink).
186///
187/// Gated on `rmw-cffi`; the underlying [`Executor`] is only present
188/// when an RMW backend is linked. W5-endgame (issue 0843): the MACRO install
189/// path (per-class static storage, placed cells, slabbed ctxs) is alloc-free,
190/// so the module no longer demands `alloc` — only the dynamic
191/// `ExecutorNodeRuntime` half does, item-gated inside.
192#[cfg(feature = "rmw-cffi")]
193pub mod node_runtime;
194pub mod runtime_storage;
195
196/// Issue 1535 — what a `void*` executor handle crossing the language seam is,
197/// and the one check every seam in this crate makes before treating it as an
198/// `Executor` (a C/C++ entry's handle is an nros-cpp context, not one).
199pub mod executor_handle;
200
201/// Phase 212.L.5 — top-level init API; phase-427 W9 — on every target.
202///
203/// Re-exported flat at the crate root: `nros::Context` (`alloc`),
204/// `nros::ContextSource`, `nros::InitError`, `nros::InitOptions` (every
205/// target); `nros::init()`, `nros::init_with_args(args)` (`env`).
206/// The `env` feature gates only the process-environment reader and the
207/// argv entry; freestanding, `Context::default_from_env()` is the
208/// bake (`Context::baked()`).
209pub mod init;
210
211/// issue 0687 — the hosted edge of configuration: every env var nano-ros
212/// honours is read here, and the core takes values.
213///
214/// phase-359 W10 stage B — read through the C library's `getenv`, so it needs
215/// a C library and not `std`: `target_os = "linux"` is "a C library is linked"
216/// on this tree. It exists on every Linux build, `env` or not, because the
217/// C / C++ entries of a `#![no_std]` image inside a host process
218/// (threadx-linux, freertos-posix — issue 1763) resolve their boot config
219/// through it. The `env` FEATURE is the user-facing half: `from_env`, `init*`,
220/// `Context::new`.
221#[cfg(target_os = "linux")]
222pub mod env;
223
224/// phase-484 W8c — the retired env aliases the hosted reader refuses.
225pub mod retired_env;
226pub use retired_env::RETIRED_ENV_VARS;
227
228#[cfg(feature = "env")]
229pub use env::ExecutorConfigEnvExt;
230#[cfg(target_os = "linux")]
231pub use env::rmw_selector;
232
233pub use init::{ContextSource, InitError, InitOptions};
234
235#[cfg(feature = "alloc")]
236pub use init::Context;
237
238#[cfg(feature = "env")]
239pub use init::{init, init_with_args};
240
241/// Compile-time opaque storage sizes for FFI consumers.
242///
243/// See [`sizes`] for the `export_size!` pattern used to expose these values
244/// to `nros-c` / `nros-cpp` at build time.
245pub mod sizes;
246
247/// Monotonic time for portable node code (issue #504).
248///
249/// phase-359 W10 — `rmw-cffi`, not `any(std, rmw-cffi)`: the clock is the
250/// platform port's, and a build with no port has none to offer.
251#[cfg(feature = "rmw-cffi")]
252pub mod time;
253
254/// CDR encapsulation constants and helpers for FFI layers that handle raw
255/// CDR bytes (e.g. nros-c, nros-cpp action and service paths).
256pub mod cdr {
257 pub use nros_serdes::{
258 CDR_BE_HEADER, CDR_HEADER_LEN, CDR_LE_HEADER, strip_cdr_header, write_cdr_le_header,
259 };
260}
261
262// Re-export core types
263pub use nros_core::{
264 CdrReader, CdrWriter, Clock, ClockType, DeserError, Deserialize, Duration, MessageInfo,
265 PUBLISHER_GID_SIZE, REQUEST_WRITER_GUID_SIZE, RawMessageInfo, RequestId, RosMessage,
266 RosService, SecNanosecMsg, SerError, Serialize, ServiceInfo, Time,
267};
268
269// -----------------------------------------------------------------------------
270// Logging (phase-417 W4.d, issue 0589).
271//
272// `nros_log` was in this crate's dependency graph and reachable from NONE of
273// its surface: no `pub use`, nothing in the prelude. So from the façade the
274// shortest path to "print something" was `std::println!` — which is FATAL on
275// Zephyr `native_sim`: `zvfs_write(1, …)` dispatches to
276// `stdinout_write_vmeth`, which calls `zvfs_write(1, …)` again, and because
277// `k_mutex` is recursive it exhausts the stack instead of deadlocking. The
278// image dies with no message. A logging facade nobody can reach from the
279// umbrella is not a facade.
280//
281// `nros::Logger` IS RE-BOUND, and that is the point rather than a side effect.
282// It used to name `nros_core::logger::Logger`, which formats through the `log`
283// crate: on a target with no `log` backend installed — every embedded one —
284// the record is constructed and dropped, and it NEVER reaches
285// `nros_platform_log_write`. The type the C and C++ FFI hands out, the type
286// the `nros_*!` macros dispatch through, and the type the platform sink
287// receives is `nros_log::Logger`. One name, one meaning.
288//
289// Migration: the two types share `new` and `name` and NOTHING else — the old
290// one has inherent `.info(&str)` / `.warn(&str)` / `*_once` / `*_throttle`
291// methods, the new one is driven by the `nros_*!` macros. So every call site
292// that used the old surface FAILS TO COMPILE against the new binding rather
293// than silently changing where its records go. That is the RFC-0089 rule
294// applied to our own rename, and it is why this is a re-binding with a named
295// escape hatch instead of a quiet swap. The escape hatch was ~~`LogCrateLogger`~~,
296// deprecated on arrival and deleted by phase-482 W6.
297pub use nros_log::{
298 DEFAULT_LOGGER, LogSink, Logger, MAX_LOGGERS, Record as LogRecord, Severity, ThrottleState,
299 get_logger, get_or_create_logger, register_logger,
300};
301// The emission macros, at the crate root, because a user who has
302// `use nros::prelude::*` should not have to learn a second crate name to log a
303// line. `#[macro_export]` puts them at `nros_log`'s root; this puts them at
304// ours.
305//
306// NAMES (phase-417, RFC-0089 "each language follows ITS OWN upstream",
307// settled 2026-09-04). The three ROS 2 client libraries disagree about how to
308// spell a log line — `rclrs::log_info!`, `RCLCPP_INFO`,
309// `RCUTILS_LOG_INFO_NAMED` — so there is no one "ROS 2 spelling" to match.
310// Rust follows rclrs, because a node ported from rclrs is read beside rclrs.
311// Hence the five severity macros below carry rclrs's names.
312//
313// Two families keep OUR prefix, and that is a decision rather than an
314// oversight: a name takes rclrs's spelling exactly when rclrs HAS that name.
315//
316// * `nros_trace!` — rclrs stops at `debug`. TRACE has no upstream twin, so
317// giving it one would claim a correspondence that does not exist.
318// * `nros_*_throttle!` / `nros_*_throttle_at!` — rclrs throttles with a
319// MODIFIER on one macro (`log_info!(logger.throttle(d), "…")` via
320// `LogParams` / `ToLogParams`), not a macro per severity. Adopting that is
321// a shape change, not a rename; see `nros-log/src/macros.rs`'s throttle
322// note for the three ledger rows it would have to reverse.
323pub use nros_log::{
324 log_debug, log_error, log_fatal, log_info, log_warn, nros_debug_throttle,
325 nros_debug_throttle_at, nros_error_throttle, nros_error_throttle_at, nros_fatal_throttle,
326 nros_fatal_throttle_at, nros_info_throttle, nros_info_throttle_at, nros_trace,
327 nros_trace_throttle, nros_trace_throttle_at, nros_warn_throttle, nros_warn_throttle_at,
328};
329/// The whole logging facade — sinks, the early-record ring, the throttle
330/// primitives, `init`/`flush`/`add_sink`.
331///
332/// The root re-exports above are the surface a node author needs; this is
333/// everything, for a board or a bridge composing its own delivery.
334pub mod logging {
335 pub use nros_log::*;
336}
337
338// Re-export heapless for generated message types and examples
339pub use nros_core::heapless;
340
341// phase-515 R1 — rclrs's per-entity options, beside R2's node options.
342pub use nros_node::{IntoPrimitiveOptions, PrimitiveOptions};
343
344// Phase 212.M.5.a.2 — executor-backed runtime entry points.
345// (~~`component_register_symbol`~~ retired in the Phase 212.N.7 closing
346// sweep — the helper had no live callers after the BSP baker + macro
347// extern emit were deleted.)
348pub use node::{
349 Callback, CallbackCtx, EntityBounds, IntoNodeOptions, NodeActionClient, NodeActionServer,
350 NodeDeclError, NodeOptions, NodeParameter, NodePublisher, NodeResult, NodeRuntime,
351 NodeServiceClient, NodeServiceServer, NodeSubscription, NodeTimer, TickCtx,
352};
353// phase-483 W3 — the component API registers into a live executor, so it
354// exists where the executor does.
355#[cfg(feature = "rmw-cffi")]
356pub use node::{CallbackEffects, Component, DeclarativeNode, ExecutableNode, NodeContext};
357// Issue 0784 — the macro and codegen PLUMBING, out of the documented surface.
358// `nros::` publishes three audiences under one namespace; these six are for the
359// `nros::node!` expansion and the generated metadata probe
360// (`nros-cli-core/src/orchestration/metadata_build.rs`), never for a node
361// author, and none is named by any hand-written file in `examples/` or
362// `packages/`. Hidden, not deleted: the generated code reaches them by path.
363// phase-359 W8 — follows `node_metadata`'s re-gate: the type needs `alloc`,
364// not `std`.
365#[cfg(feature = "alloc")]
366pub use node_metadata::SourceMetadataExport;
367/// phase-381 follow-up — the node-FQN join, at the crate root because that is
368/// where a user looks after `Executor::get_node_names` hands them the two
369/// halves. `nros_node::names` also has `expand_name` / `resolve_name`, which
370/// stay behind `node_metadata` — they are the entity-name seam, not this.
371pub use node_metadata::fully_qualified_name;
372pub use node_metadata::{
373 CallbackEffectKind, CallbackEffectMetadata, EntityKind, EntityMetadata, MetadataRecorder,
374 MetadataString, NodeMetadata, NodeMetadataError, ParameterDefault, SourceLocationMetadata,
375 SourceNameKind,
376};
377#[doc(hidden)]
378pub use node_metadata::{CallbackId, EntityId, NodeId};
379// Phase 216.A.4 — opaque tag types Node authors hold on `Self::State`
380// and match against the `Callback<'_>` delivered to
381// `ExecutableNode::on_callback`.
382pub use dispatch_tag::{ActionTag, ServiceTag, SubscriptionTag};
383// W5-endgame (issue 0843) — the alloc-free half of the seam: per-class static
384// storage. The `_in` installs the macro emits live in `__macro`.
385#[cfg(feature = "rmw-cffi")]
386pub use node_runtime::ComponentSlotStorage;
387// The dynamic runtime still needs `alloc`.
388#[cfg(all(feature = "rmw-cffi", feature = "alloc"))]
389pub use node_runtime::{ExecutorError, RegisteredNode};
390
391/// What `nros::node!`, `nros::main!`, the generated entry points and the board
392/// entry glue expand into — never what a node author writes.
393///
394/// RFC-0071 D12 (phase-506 W1, issue 1789). These sat at the top of `nros::`
395/// beside the user API, public only because generated code resolves them
396/// through `::nros::`. Gathering them here keeps every path reachable for
397/// generated code and takes them off the surface a porting user browses.
398#[doc(hidden)]
399pub mod __macro {
400 #[cfg(feature = "rmw-cffi")]
401 pub use crate::node::{__record_with_context, record_node_metadata, register_node};
402 pub use crate::node::{ActionExecutor, ClientDispatch, PublisherResolver, TimerOp};
403 // Phase 257 (W0-B) / phase-305 W3 — the uniform cross-language
404 // component-install seam backing `__nros_component_<pkg>_install`.
405 #[cfg(all(feature = "rmw-cffi", feature = "alloc"))]
406 pub use crate::node_runtime::{
407 ExecutorNodeRuntime, install_node_typed, install_node_typed_with_launch,
408 install_node_typed_with_node_identity, install_node_typed_with_params,
409 };
410 #[cfg(feature = "rmw-cffi")]
411 pub use crate::node_runtime::{install_node_typed_in, install_node_typed_with_launch_in};
412 // phase-509 P5 -- a ranked node's rank, seeded before its `register`.
413 #[cfg(feature = "rmw-cffi")]
414 pub use crate::node_runtime::seed_node_rank;
415}
416
417// phase-483 W3 — the `install_node_typed*` STUBS for builds without
418// `rmw-cffi` are gone. They existed so a `nros::node!()` package compiled
419// without the executor stayed linkable; a component now registers into a live
420// executor (`NodeContext::create_node` returns a `Node`), so a component
421// package without `rmw-cffi` has nothing to register into and does not build.
422// Phase 212.N.12 — canonical `nros::node!()` macro. Replaces the legacy
423// `nros::node!()` macro (retired in the N.12 hard rename — both the
424// proc-macro forwarder and the Cargo metadata key are gone).
425#[cfg(feature = "macros")]
426pub use nros_macros::node;
427// Phase 212.N.9 — `nros::main!()` proc-macro family. One-line Entry-pkg
428// `main.rs` (replaces the legacy `build.rs + include!()` shape). See
429// `docs/design/0024-multi-node-workspace-layout.md` §11.6.
430#[cfg(feature = "macros")]
431pub use nros_macros::main;
432
433/// Route this image's panics to its platform — phase-366 W5.c / RFC-0077.
434///
435/// Emits the `#[panic_handler]` for an embedded image, forwarding the message
436/// to `nros_platform_panic` so a Rust panic ends the same way a C precondition
437/// failure or a C++ terminate does. What that ending IS belongs to the port:
438/// `k_panic()` on Zephyr, `esp_system_abort()` on ESP-IDF, UART-then-exit-QEMU
439/// on the ThreadX RV64 board.
440///
441/// # Why this is a macro you INVOKE, not something `nros::main!` emits
442///
443/// `#[panic_handler]` is a singleton of the final artifact, and the image owns
444/// it. Emitting one silently from `nros::main!()` would collide with every image
445/// that already declares its own — `examples/esp32-c3-baremetal` writes
446/// `use esp_backtrace as _;`, and `logging-smoke-freertos-mps2` uses
447/// `panic-semihosting` with `features = ["exit"]` so a panic exits QEMU instead
448/// of hanging the test harness. Those images are RIGHT, and an invisible default
449/// would fight them.
450///
451/// So the line is written in the entry, where it can be read, swapped for
452/// `use panic_halt as _;`, or replaced by a hand-written handler that logs to
453/// NVM and reboots. A default you cannot see is a constraint, not a default.
454///
455/// # Use — `main!(panic = …)` is the normal way; this is the escape hatch
456///
457/// phase-366 R3. An entry that goes through `nros::main!()` should say
458/// `panic = "platform"` (or nothing — that is the default since M5) and let the
459/// macro emit this body. One line, and the build can check it.
460///
461/// ```ignore
462/// #![no_std]
463/// nros::main!(); // ends through `nros_platform_panic`
464/// ```
465///
466/// Invoke this macro directly only where no `main!()` expansion can carry the
467/// item — a hand-rolled `no_std` binary, or the lib side of a crate whose
468/// `crate-type` includes `staticlib` and whose entry macro is
469/// `zephyr_component_main!` / a board `app_main!`. It is kept for exactly those:
470/// deleting it would strand the images that cannot use the macro replacing it.
471///
472/// ```ignore
473/// // src/app_main.rs — the .a is a final artifact to rustc, and `main!()` in
474/// // the bin target never reaches it.
475/// nros::panic_to_platform!();
476/// ```
477///
478/// Do NOT invoke it in a `std` image: libstd supplies the lang item there and a
479/// second one does not compile. Do not invoke it alongside `use panic_halt as _`
480/// or any other provider, for the same reason — that is the duplicate
481/// `check-archive-lang-items` exists to catch.
482/// Park the core on panic — the `halt` value of `nros::main!(panic = …)`.
483///
484/// For an image that must not print: no formatting, no allocation, no call out
485/// to the platform. Interrupts are masked first so the parked core cannot be
486/// woken back into a half-dead system by a timer or a driver ISR still armed
487/// from before the panic.
488///
489/// This is a body rather than a re-export of the `panic-halt` crate so that
490/// choosing it costs the entry no new dependency — `main!(panic = "halt")` is a
491/// word in a macro the crate already calls, which is the point of the surface.
492/// The behaviour is the same: mask, then spin forever.
493///
494/// Prefer `panic = "platform"`. Halting discards the diagnosis, and every port
495/// implements `nros_platform_panic` precisely so a dying image can say why.
496/// Phase 392 W3b — the receive-buffer size for a message type, as a constant
497/// that cannot drift from the type.
498///
499/// ```ignore
500/// node.subscription::<PointCloud2>("points")
501/// .rx_buffer::<{ nros::rx_buffer_for!(PointCloud2) }>()
502/// .build(on_cloud)?;
503/// ```
504///
505/// `.rx_buffer::<N>()` has always accepted a number. The problem is where the
506/// number comes from: a literal is correct until someone appends a field to the
507/// message, and then it is silently too small — the sample is received, ACKed
508/// and dropped at the transport, which needs a packet capture to attribute
509/// (`report_dropped_take`, and the 13.4 KiB Autoware trajectory case). This
510/// expands to the type's own bound, computed from its schema by phase 380, so
511/// appending a field moves the buffer with it.
512///
513/// **phase-392 W3c narrowed what this is FOR.** A plain
514/// `.typed::<M>().build()` now derives its arena receive buffer from `M` on its
515/// own, and `.rx_buffer::<N>()` is the opt-out, so the macro is no longer how a
516/// buffered subscription gets a type-sized buffer. What still needs it is the
517/// entry shapes whose buffer is a real inline `[u8; RX]` and therefore a
518/// const-generic argument: `.message_info()` and `.safety()`. Those are the
519/// call sites this expands for.
520///
521/// **Why a macro and not a method.** The builder cannot do this for you:
522/// inside `impl<M> TypedSubscriptionBuilder<M>` the type is a generic
523/// parameter, and on stable Rust a generic parameter may not appear in a const
524/// operation — `error: generic parameters may not be used in const operations`.
525/// At a call site the type is CONCRETE, which is legal, so the size has to be
526/// named where the type is. That constraint is also why phase-392's size
527/// classes were "decoupled from codegen" in the first place.
528///
529/// **Unbounded types are a BUILD ERROR** (phase-403 W0). A type with an
530/// unbounded `string`/`wstring`/`sequence` has no bound, and this refuses to
531/// invent one. It used to expand to `DEFAULT_RX_BUF_SIZE`; it now fails to
532/// compile, naming both remedies.
533///
534/// Every message type is REQUIRED to carry a derived upper bound, stated in the
535/// `.msg` (`string<=64`) or capped in `nros-codegen.toml`. Phase 380 is explicit
536/// that `None` means "no bound EXISTS", never "unknown", and that a buffer must
537/// not be sized from a fallback. Substituting the configured default was the
538/// violation of that rule; refusing is what it licenses. `report_dropped_take`
539/// is a backstop for a buffer that is too small, not a licence to pick one.
540///
541/// ```compile_fail
542/// use nros_serdes::schema::{Field, FieldType, Message};
543/// struct Unbounded;
544/// impl Message for Unbounded {
545/// const TYPE_NAME: &'static str = "test/msg/Unbounded";
546/// const FIELDS: &'static [Field] = &[Field {
547/// name: "s",
548/// ty: FieldType::String,
549/// offset: 0,
550/// }];
551/// }
552/// let _n: usize = nros::rx_buffer_for!(Unbounded);
553/// ```
554///
555/// The positive control for that `compile_fail`, so it cannot pass because the
556/// fixture stopped compiling for an unrelated reason:
557///
558/// ```
559/// use nros_serdes::schema::{Field, FieldType, Message};
560/// struct Bounded;
561/// impl Message for Bounded {
562/// const TYPE_NAME: &'static str = "test/msg/Bounded";
563/// const FIELDS: &'static [Field] = &[Field {
564/// name: "a",
565/// ty: FieldType::Uint64,
566/// offset: 0,
567/// }];
568/// }
569/// let n: usize = nros::rx_buffer_for!(Bounded);
570/// assert!(n > 0);
571/// ```
572///
573/// **Why the `const` block.** The macro is used in two positions: as a
574/// const-generic argument (`.rx_buffer::<{ rx_buffer_for!(M) }>()`) and as a
575/// plain expression (`let n = rx_buffer_for!(M);`). A bare `panic!` is a build
576/// error in the first and a RUNTIME panic in the second, which would make the
577/// rule depend on where the macro appears. Wrapping the whole match in an inline
578/// `const` block forces compile-time evaluation in BOTH, so an unbounded type
579/// can never reach a running image.
580///
581/// **What the error names.** rustc points at this macro invocation, where the
582/// type is written literally, so the TYPE is named. The MEMBER that costs the
583/// bound cannot be in the message: const evaluation does not format, so a
584/// `panic!` there takes a literal only. The member is named by the codegen
585/// diagnostic for the same type -- `unbounded_reason` in the generated C header
586/// (`packs/c/message.h.jinja`), which names EVERY member that costs the bound in
587/// one build (phase-403 W0), or `nros_serdes::size::visit_unbounded` over its
588/// `FIELDS`.
589#[macro_export]
590macro_rules! rx_buffer_for {
591 ($msg:ty) => {
592 // The `const` block is load-bearing; see "Why the `const` block" above.
593 const {
594 match $crate::__rx_bound::<$msg>() {
595 ::core::option::Option::Some(n) => n,
596 ::core::option::Option::None => ::core::panic!(
597 "nros: this message type has NO serialized-size bound, so no \
598 receive buffer can be sized from it.\n\
599 Every message type must carry a derived upper bound. Bound the \
600 member that costs it, either:\n\
601 \x20 - in the `.msg`: `string<=64`, `wstring<=64`, \
602 `sequence<T, N>`, `T[<=N]`; or\n\
603 \x20 - as an INLINE `cap` in `nros-codegen.toml`: under \
604 `[fields]`, `\"pkg/Msg.field\" = 64`. A `heap` or `view` cap \
605 is a sizing hint that nothing enforces (RFC-0033), so it \
606 deliberately does NOT bound.\n\
607 WHICH members cost the bound -- all of them, not just the \
608 first -- are named by the codegen diagnostic for this same \
609 type: `unbounded_reason` in the generated C header, or \
610 `nros_serdes::size::visit_unbounded` over its `FIELDS`. The type itself is named by the \
611 `rx_buffer_for!` invocation rustc points at.\n\
612 Phase 380: `None` means no bound EXISTS, never \"unknown\", and \
613 a buffer sized from a fallback is the failure that rule was \
614 written to prevent. Erroring honours it; defaulting to \
615 `DEFAULT_RX_BUF_SIZE` did not."
616 ),
617 }
618 }
619 };
620}
621
622#[macro_export]
623macro_rules! panic_halt {
624 () => {
625 #[panic_handler]
626 fn __nros_panic_halt(_info: &::core::panic::PanicInfo) -> ! {
627 // Mask interrupts through the platform's critical section, which is
628 // the one IRQ primitive that is portable across the ports (the
629 // `cortex_m`/`riscv` intrinsics are not). Entering and never
630 // leaving is deliberate.
631 unsafe extern "C" {
632 fn nros_platform_critical_section_acquire() -> u32;
633 }
634 // SAFETY: the ABI's acquire takes no argument and returns a restore
635 // token we deliberately drop — nothing after this point runs.
636 unsafe {
637 let _ = nros_platform_critical_section_acquire();
638 }
639 loop {
640 ::core::hint::spin_loop();
641 }
642 }
643 };
644}
645
646#[macro_export]
647macro_rules! panic_to_platform {
648 () => {
649 #[panic_handler]
650 fn __nros_panic(info: &::core::panic::PanicInfo) -> ! {
651 use ::core::fmt::Write as _;
652
653 // Fixed buffer, never the heap: this runs when the allocator may be
654 // exactly what failed. Truncation is deliberate — a short panic line
655 // still diagnoses; a missing one does not.
656 struct Buf {
657 bytes: [u8; 192],
658 used: usize,
659 }
660 impl ::core::fmt::Write for Buf {
661 fn write_str(&mut self, s: &str) -> ::core::fmt::Result {
662 let room = self.bytes.len() - self.used;
663 let n = s.len().min(room);
664 self.bytes[self.used..self.used + n].copy_from_slice(&s.as_bytes()[..n]);
665 self.used += n;
666 Ok(())
667 }
668 }
669
670 let mut buf = Buf {
671 bytes: [0u8; 192],
672 used: 0,
673 };
674 let _ = write!(buf, "{info}");
675
676 unsafe extern "C" {
677 fn nros_platform_panic(msg: *const u8, len: usize) -> !;
678 }
679 // SAFETY: `buf.bytes[..used]` is initialised and outlives the
680 // diverging call; the ABI takes a length-delimited diagnostic, not
681 // a C string.
682 unsafe { nros_platform_panic(buf.bytes.as_ptr(), buf.used) }
683 }
684 };
685}
686
687/// Define Zephyr's `rust_main` for a self-bringup Rust component package.
688///
689/// The macro is intended for `rust_cargo_application()` apps whose crate
690/// already invokes `nros::node!()`. It opens a Zephyr executor, registers
691/// the supplied component through `nros::node_runtime::ExecutorNodeRuntime`, and spins forever.
692/// Issue 0330 — force-link an RMW backend crate into a pure-Rust staticlib.
693///
694/// On a Rust-only image (Zephyr and friends) the Zephyr module emits a weak
695/// `nros_rmw_<name>_register` and calls it only if it resolves. The strong
696/// definition is the backend crate's `#[no_mangle]` export — and rustc's
697/// staticlib DCE drops it unless something in the crate being compiled into the
698/// staticlib references that crate. The symbol is then present in the rlib and
699/// absent from the `.a`, the weak call sees NULL, and the image comes up with no
700/// backend registered (issues 0155 / 0163).
701///
702/// This emits the reference, without naming any backend in nano-ros' own
703/// RMW-agnostic layers — the app crate names it, because the app crate is what
704/// selects an RMW:
705///
706/// ```ignore
707/// #[cfg(feature = "rmw-zenoh")]
708/// nros::force_link_backend!(nros_rmw_zenoh);
709/// #[cfg(feature = "rmw-xrce")]
710/// nros::force_link_backend!(nros_rmw_xrce_cffi);
711/// ```
712///
713/// It is an ANCHOR, not a registration call — the static is never executed
714/// (same class as `nros-c`'s `FORCE_LINK` and `nros-rmw-cffi`'s section anchor).
715/// Registration happens through `nros_app_register_backends`. Backends whose
716/// register entry lives in a C/C++ library the image already links (cyclonedds
717/// on Zephyr) need no anchor at all.
718///
719/// Invoke at module scope. Multiple invocations in one crate are fine — each
720/// expands inside its own anonymous const, so the static names cannot collide.
721#[macro_export]
722macro_rules! force_link_backend {
723 // `ident`, not `path`: a `path` fragment may not be followed by `::`, so
724 // `$backend::register()` fails to parse at the CALL site with a misleading
725 // "expected an operator". Backend crate names are single idents anyway.
726 ($backend:ident) => {
727 const _: () = {
728 #[used]
729 static __NROS_FORCE_LINK_BACKEND: fn() = || {
730 let _ = $backend::register();
731 };
732 };
733 };
734}
735
736// Phase 248 C7 (Method A) — gated on `rmw-cffi` only (needs `Executor`), NOT a
737// `platform-*` feature. This is a framework ENTRY macro (same category as
738// `nros::main!`'s zephyr `rust_main` codegen) — `#[macro_export]` so it emits
739// nothing unless a Zephyr example invokes it; the body's `::zephyr::*` /
740// `::nros_platform::zephyr::wait_network` resolve only in that zephyr-build
741// context (the example deps the `zephyr` crate + `nros-platform[platform-zephyr]`).
742#[cfg(feature = "rmw-cffi")]
743#[macro_export]
744macro_rules! zephyr_component_main {
745 ($node:ty) => {
746 #[unsafe(no_mangle)]
747 pub extern "C" fn rust_main() {
748 // issue 1123 — publish an `nros_log` sink list. The `log` FACADE
749 // (what an example body writes) is bridged into it just below
750 // (issue 1324 retired `zephyr::set_logger`); the sink list is what
751 // `nros_log` needs, which is what the
752 // FRAMEWORK writes (nros-node's executor, nros-rmw-zenoh's session
753 // and pool diagnostics, nros-rmw-cffi). With no sink list
754 // `nros_log::dispatch_to_sinks` holds each record in the bounded
755 // `nros_log::early` ring — issue 0710 deliberately removed the
756 // implicit platform fallback — so on a pure-Rust Zephyr image every
757 // framework record was constructed, dispatched and never seen.
758 //
759 // This is the funnel: a pure-Rust Zephyr image has no
760 // `libnros_c.a` (issue 0163), so `rclc_log_init` / `rclc_log_emit`'s
761 // lazy `ensure_default_sinks()` never run, and `nros-board-zephyr`
762 // covers only `run_tiers`. Reached through `nros_platform` because
763 // the example leaves dep that and not `nros-platform-cffi`.
764 //
765 // FIRST, before `wait_network`: `init` DRAINS the early ring, and
766 // anything raised during bringup is exactly what the ring exists to
767 // serve (the esp32 half of this class, issue 1048, got this order
768 // backwards and drained into a writer that was still `None`). The
769 // Zephyr writer is `nros_platform_log_write`, defined
770 // unconditionally by the C port, so there is no such window here.
771 ::nros_platform::log::init_default();
772 // issue 1324 — the `log` facade into the same sinks. This was
773 // `zephyr::set_logger()`, whose full-`CONFIG_LOG` arm exists only
774 // under `CONFIG_RUST_ALLOC`, the allocator this image no longer links.
775 ::nros_platform::log::install_log_crate_bridge();
776 // Phase 248 C7 step 1 — relocated helper (was `$crate::platform::zephyr`).
777 let _ = ::nros_platform::zephyr::wait_network(2000);
778 // Phase 249 P1 — RMW register is board/platform-owned (Phase 248 C5a);
779 // the backend-agnostic `nros` crate cannot register (no backend dep).
780 // Issue 0155 — the "board/platform boot path" that was supposed to
781 // register never fired for pure-Rust Zephyr images: the zephyr
782 // module emits a STRONG `nros_app_register_backends` stub for the
783 // Kconfig-selected RMW (zephyr/CMakeLists.txt Phase 160.A), but
784 // only the C/C++ `nros_cpp_init` path ever CALLED it — a Rust-only
785 // image reached `Executor::open` with no backend registered and
786 // died with Transport(ConnectionFailed) (silently, pre-0155).
787 // Call the hook explicitly, exactly like the C++ init path.
788 unsafe extern "C" {
789 fn nros_app_register_backends();
790 }
791 unsafe { nros_app_register_backends() };
792 // Issue 2051 (RFC-0109 D8) — the session properties the bringup's
793 // RMW file lowers to, baked by this crate's build from the table
794 // the west lane names, installed before any session opens. The
795 // baked Kconfig locator yields to them below (RFC-0109 D9), so the
796 // file is the wiring here as on every road.
797 if !$crate::__macro_support::baked_session::SESSION_PROPERTIES.is_empty()
798 && $crate::__macro_support::set_session_properties(
799 &$crate::__macro_support::baked_session::SESSION_PROPERTIES,
800 )
801 .is_err()
802 {
803 ::core::panic!(
804 "the bringup's RMW file lowers to more session properties than the executor holds"
805 );
806 }
807 // Issue 0163 — a pure-Rust image has no `libnros_c.a`, so the
808 // backend must ride in THIS staticlib and be referenced from the
809 // app crate, or rustc's staticlib DCE drops the whole backend
810 // closure (the `#[no_mangle]` C export included), leaving the
811 // module's weak `nros_rmw_<x>_register` resolving to NULL and the
812 // hook above registering nothing.
813 //
814 // Issue 0330 — that reference used to be a pair of hardcoded
815 // `::nros_rmw_zenoh::register()` / `::nros_rmw_xrce_cffi::register()`
816 // calls emitted RIGHT HERE, which named two concrete backends in
817 // the RMW-agnostic facade (and left cyclonedds handled asymmetrically
818 // through the C hook). It also forced every consumer to carry
819 // `rmw-zenoh` / `rmw-xrce` feature rows purely so these `cfg`s would
820 // resolve — the cyclonedds-only example carried both as inert
821 // placeholders. The anchor now lives in the app crate, which is the
822 // layer that legitimately selects an RMW: see
823 // [`nros::force_link_backend!`]. Registration itself is unchanged —
824 // the `nros_app_register_backends` hook above does it.
825 // phase-427 W9 — the baked identity has ONE source. This block
826 // used to read `option_env!("NROS_LOCATOR")` / `NROS_DOMAIN_ID`
827 // itself and assemble an `ExecutorConfig` by hand (a copy
828 // `nros::main!`'s Zephyr arm carried too, minus the domain — issue
829 // 0161's class); `Context::baked()` is where that lives now, and
830 // `default_from_env()` resolves to it on a freestanding build.
831 // `nros`'s own `build.rs` re-exports the Kconfig values into that
832 // crate's build (`nros_zephyr_build::bake_nros_config`), the same
833 // channel the leaf's `build.rs` uses for itself.
834 //
835 // #166 / phase-286 W1 — native_sim test parallelism. The test
836 // harness launches the image with `-testargs --nros-locator=<loc>`
837 // and starts a per-test zenohd on that (ephemeral) port; preferring
838 // it over the build-time bake lets every test dial a DISTINCT router,
839 // retiring the shared-baked-port serialization of the zenoh e2e
840 // lanes. Provided by `nros-platform-zephyr` (argv-backed, process
841 // lifetime); returns NULL on real embedded → the bake stands. The
842 // hook is an `extern "C"` only a Zephyr image defines, so it is
843 // resolved HERE, in the entry, and handed to the context.
844 unsafe extern "C" {
845 fn nros_runtime_locator_override() -> *const ::core::ffi::c_char;
846 }
847 let runtime_locator: ::core::option::Option<&str> = {
848 let p = unsafe { nros_runtime_locator_override() };
849 if p.is_null() {
850 ::core::option::Option::None
851 } else {
852 unsafe { ::core::ffi::CStr::from_ptr(p) }.to_str().ok()
853 }
854 };
855 let ctx = match $crate::Context::default_from_env() {
856 ::core::result::Result::Ok(ctx) => ctx
857 .yield_locator_to_session_properties(
858 &$crate::__macro_support::baked_session::SESSION_PROPERTIES,
859 )
860 .with_locator_override(runtime_locator),
861 ::core::result::Result::Err(e) => {
862 panic!("nros zephyr entry: baked Context is invalid: {e:?}")
863 }
864 };
865 let config = ctx.config(<$node as $crate::Component>::NAME);
866 // Issue 0155 — fail LOUD (repo rule: panic, not silent
867 // early-return). A silent `return` here idles the image with zero
868 // output; the zephyr-cyclonedds rust lane was undiagnosable until
869 // this printed the real error.
870 let executor = match $crate::Executor::open(&config) {
871 Ok(executor) => executor,
872 Err(e) => {
873 panic!("nros zephyr entry: Executor::open failed: {e:?}");
874 }
875 };
876 let mut runtime = $crate::__macro::ExecutorNodeRuntime::from_executor(executor);
877 // phase-436 — Zephyr's microsecond park, which the tier runners
878 // install on every tier and this single-executor entry did not.
879 // Zephyr's `platform.c` always defines both symbols, and this macro
880 // only expands in a Zephyr image, so they are plain references.
881 {
882 unsafe extern "C" {
883 fn nros_platform_wake_park_until_us(
884 w: *mut ::core::ffi::c_void,
885 deadline_us: u64,
886 ) -> i8;
887 fn nros_platform_wake_park_granularity_us() -> u64;
888 }
889 // SAFETY: a pure query of the linked port, no arguments.
890 let granularity = unsafe { nros_platform_wake_park_granularity_us() };
891 let _ = $crate::port_park::install_port_park(
892 runtime.executor_mut(),
893 nros_platform_wake_park_until_us,
894 granularity,
895 );
896 }
897 if let Err(e) = runtime.register_node::<$node>() {
898 panic!("nros zephyr entry: register_node failed: {e:?}");
899 }
900 // Readiness marker. The C/C++ Zephyr listeners print
901 // "Waiting for messages..." from their `main()` before the spin
902 // loop; the e2e harness polls for that substring to know the
903 // subscriber has declared before starting the talker (Phase 89.12).
904 // The Rust path's spin loop lives in this macro (the node only owns
905 // callbacks), so emit the same canonical marker here — without it a
906 // fully-working Rust listener never signals readiness and the e2e
907 // times out at 30 s (issue #35: the zenoh native_sim rust pubsub /
908 // service / action failures were this missing marker, not a
909 // transport fault — `Executor::open` + `register_node` had already
910 // succeeded).
911 ::log::info!("Waiting for messages");
912 loop {
913 // phase-521 W6 (RFC-0113 D2) — park until the next deadline when
914 // every source of work can end the park, else the 10 ms slice.
915 let budget = runtime.entry_spin_budget();
916 let _ = runtime.spin_once(budget);
917 }
918 }
919 };
920}
921
922// phase-483 W4 (issue 0784) — `StandaloneNode` (the transport-less
923// pre-component node) and its `NodeConfig` / `PublisherHandle` /
924// `SubscriptionHandle` are no longer re-exported. `nros::Node` is THE node,
925// and these have no rclrs counterpart; the two bench programs that use them
926// import `nros_node` directly.
927
928// Re-export publisher/subscriber options (topic + QoS; always available).
929pub use nros_node::{PublisherOptions, SubscriptionOptions};
930
931// Re-export timer types
932pub use nros_node::{TimerCallbackFn, TimerDuration, TimerHandle, TimerMode, TimerState};
933// phase-425 W4/W5 — which clock advances a timer. Re-exported here for the
934// reason `sim-time` is a feature here: an application deps `nros`, not
935// `nros-node`, so a capability that stops at the core crate is one no
936// application can name. W4 added the type and the registrar and stopped at the
937// core, which the `/clock` fixture found the moment it tried to use them.
938// UNGATED since phase-430 W4, and the enum moved to `nros_node::timer` to make
939// that possible. The `rmw-cffi` gate was right while the only way to name a
940// clock was `Executor::register_timer_on_clock`; W4 put the axis on the
941// DECLARATIVE surface too (`DeclaredNode::create_timer_on_clock`), which a
942// metadata-mode build compiles with no transport linked. A type the metadata
943// layer cannot name is a type the metadata layer re-invents.
944pub use nros_node::timer::TimerClockSource;
945// Issue 2038 — what a declared timer's callback reads through
946// `CallbackCtx::timer_info` (issue 1974's call info). Ungated for the reason
947// `TimerClockSource` is: the declarative surface compiles in metadata mode.
948pub use nros_node::timer::TimerCallInfo;
949
950/// phase-467 Q4 / ledger row `c:logging_rosout_enabled` — the `/rosout` log
951/// bridge. A module re-export rather than a flat one: the names a caller
952/// needs (`enable`, `pump`, `qos`, `qos_bounded`, `TOPIC`) are generic words
953/// that only read correctly under the `rosout::` prefix.
954/// Gated like the module it comes from: `nros_node::rosout` is
955/// `#[cfg(all(feature = "rosout", any(has_rmw, test)))]` there, and `has_rmw`
956/// is `nros-node`'s own build-script cfg — this crate spells the same
957/// condition as `feature = "rmw-cffi"`, exactly as the `parameter_services`
958/// re-export below does.
959#[cfg(all(feature = "rosout", feature = "rmw-cffi"))]
960pub use nros_node::rosout;
961
962// The QoS vocabulary a node author states. RFC-0071 D12 (phase-506 W1, issue
963// 1789): the RMW SEAM — `Session`, `Rmw`, `Transport`, `TopicInfo`, the
964// `Publisher` / `Subscription` / `ClientTrait` / `ServiceTrait` entity traits
965// and their config types — is NOT re-exported here. It is what a backend
966// implements, and every in-tree backend already imports it from `nros-rmw`
967// directly; beside the user API it was the "three audiences" problem issue
968// 0784 was named for.
969pub use nros_rmw::{
970 QoSDurabilityPolicy, QoSHistoryPolicy, QoSLivelinessPolicy, QoSOverride, QoSOverrideRole,
971 QoSOverrideValue, QoSPolicyMask, QoSProfile, QoSReliabilityPolicy,
972};
973// `SessionMode` is the type of `ExecutorConfig`'s `mode`, so it is user
974// configuration even though it is defined beside the seam.
975pub use nros_rmw::SessionMode;
976
977/// Phase 108.B — standard ROS-2-equivalent QoS profiles. Match
978/// upstream `rmw_qos_profile_default` etc. field-by-field. Backends
979/// validate against these synchronously at create time; no silent
980/// downgrade.
981// phase-379 W5 — rclrs exports the eight QoS presets as CRATE-LEVEL consts
982// (`rclrs::QOS_PROFILE_DEFAULT`); ours were associated consts on `QoSProfile`
983// and nothing re-exported them, so `use rclrs::QOS_PROFILE_DEFAULT` had no
984// counterpart to port to. The NAMES already matched exactly — only the path
985// did not, which is why the ledger filed these as a re-export and not a
986// rename.
987//
988// These ALIAS the associated consts rather than restating them. `nros::qos`
989// below is a second, hand-written copy of the same presets that predates them;
990// ~~`qos_presets_agree`~~ in the test module asserts the two never drift, which is
991// the hand-mirror class (issues 0088/0160/0245) one layer up.
992/// `rmw_qos_profile_default` — reliable, volatile, keep-last(10).
993pub const QOS_PROFILE_DEFAULT: QoSProfile = QoSProfile::QOS_PROFILE_DEFAULT;
994/// `rmw_qos_profile_system_default` — the RMW implementation's own defaults.
995pub const QOS_PROFILE_SYSTEM_DEFAULT: QoSProfile = QoSProfile::QOS_PROFILE_SYSTEM_DEFAULT;
996/// `rmw_qos_profile_sensor_data` — best-effort, keep-last(5).
997pub const QOS_PROFILE_SENSOR_DATA: QoSProfile = QoSProfile::QOS_PROFILE_SENSOR_DATA;
998/// `rmw_qos_profile_services_default`.
999pub const QOS_PROFILE_SERVICES_DEFAULT: QoSProfile = QoSProfile::QOS_PROFILE_SERVICES_DEFAULT;
1000/// `rmw_qos_profile_parameters` — reliable, depth 1000.
1001pub const QOS_PROFILE_PARAMETERS: QoSProfile = QoSProfile::QOS_PROFILE_PARAMETERS;
1002/// `rmw_qos_profile_parameter_events`.
1003pub const QOS_PROFILE_PARAMETER_EVENTS: QoSProfile = QoSProfile::QOS_PROFILE_PARAMETER_EVENTS;
1004/// The clock preset — sensor-data shaped with depth 1.
1005pub const QOS_PROFILE_CLOCK: QoSProfile = QoSProfile::QOS_PROFILE_CLOCK;
1006/// The action-status preset — reliable + transient-local, depth 1.
1007pub const QOS_PROFILE_ACTION_STATUS_DEFAULT: QoSProfile =
1008 QoSProfile::QOS_PROFILE_ACTION_STATUS_DEFAULT;
1009
1010/// The five presets under their older, shorter names.
1011///
1012/// **Every one of these is an ALIAS.** phase-428 W10 — this module used to
1013/// RESTATE four of the five as struct literals: `DEFAULT` spelled all ten fields
1014/// out, `SENSOR_DATA` and `PARAMETERS` were `..DEFAULT` updates of it, and
1015/// `SERVICES_DEFAULT` was `= DEFAULT` — a second, independent transcription of
1016/// upstream's table living 40 lines below the first. Only `SYSTEM_DEFAULT` was a
1017/// real alias, and the comment that made it one claimed it was joining "the
1018/// other four", which was not true of any of them.
1019///
1020/// That is the mirror this work item removes: upstream's 7 profiles were
1021/// hand-written in four places across the repo — here, the crate-level
1022/// `QOS_PROFILE_*` consts above, `nros-rmw/src/traits.rs`, and
1023/// `nros-rmw-abi/include/nros/rmw_entity.h` — about 26 constants for a 7-row
1024/// table, with no gate binding them. `QOS_PROFILE_PARAMETER_EVENTS` is the proof
1025/// that the arrangement does not hold: it is `KeepAll, 0` in `traits.rs` under a
1026/// comment claiming it matched `rmw_qos_profile_parameter_events`, which is
1027/// `KEEP_LAST, 1000`.
1028///
1029/// So this module now declares no values at all. The SSoT is
1030/// [`QoSProfile`]'s associated consts, and a correction there — including the
1031/// `PARAMETER_EVENTS` one — reaches these names without anyone remembering to
1032/// come here. Note that the alias set is a SUBSET: `CLOCK`,
1033/// `PARAMETER_EVENTS`, `ACTION_STATUS_DEFAULT` and `PX4` have no short spelling
1034/// and are reached as `nros::QOS_PROFILE_*` or `QoSProfile::QOS_PROFILE_*`.
1035pub mod qos {
1036 use crate::QoSProfile;
1037
1038 /// `rmw_qos_profile_default` — reliable + volatile + keep-last(10),
1039 /// automatic liveliness, no deadline / lifespan.
1040 pub const DEFAULT: QoSProfile = QoSProfile::QOS_PROFILE_DEFAULT;
1041
1042 /// `rmw_qos_profile_sensor_data` — best-effort + volatile + keep-last(5).
1043 pub const SENSOR_DATA: QoSProfile = QoSProfile::QOS_PROFILE_SENSOR_DATA;
1044
1045 /// `rmw_qos_profile_services_default` — reliable + volatile +
1046 /// keep-last(10).
1047 ///
1048 /// It aliases its OWN associated const, not `DEFAULT`. The two profiles
1049 /// hold the same values upstream and are two upstream constants; writing
1050 /// this one as `= DEFAULT`, which is what it said before W10, would make a
1051 /// future divergence between them invisible here.
1052 pub const SERVICES_DEFAULT: QoSProfile = QoSProfile::QOS_PROFILE_SERVICES_DEFAULT;
1053
1054 /// `rmw_qos_profile_parameters` — reliable + volatile + keep-last(**1000**).
1055 pub const PARAMETERS: QoSProfile = QoSProfile::QOS_PROFILE_PARAMETERS;
1056
1057 /// `rmw_qos_profile_system_default` — **an absence, not a profile**: every
1058 /// policy is the SYSTEM_DEFAULT sentinel and the depth is the depth
1059 /// sentinel, resolved by whichever backend is linked.
1060 ///
1061 /// issue 0829 — this said `= DEFAULT` (depth 10) while
1062 /// `QoSProfile::QOS_PROFILE_SYSTEM_DEFAULT` said depth 1, so one name
1063 /// shipped two queue depths depending on which spelling a caller reached.
1064 /// It was the first of these to become an alias, and since W10 it is no
1065 /// longer the only one: see `QoSProfile::QOS_PROFILE_SYSTEM_DEFAULT`.
1066 pub const SYSTEM_DEFAULT: QoSProfile = QoSProfile::QOS_PROFILE_SYSTEM_DEFAULT;
1067}
1068
1069// Re-export safety types when feature is enabled
1070#[cfg(feature = "safety-e2e")]
1071pub use nros_rmw::{IntegrityStatus, SafetyValidator, crc32};
1072
1073// Phase 248 C7 step 1 — the `nros::platform::zephyr` module (the
1074// ~~`wait_for_network`~~ FFI wrapper) RELOCATED to `nros-platform`
1075// (`nros_platform::zephyr::wait_network`); callers reference it via
1076// `::nros_platform::zephyr::wait_network`. nros no longer hosts a platform
1077// helper module. (The `zephyr_component_main!` macro relocation is C7 step 2.)
1078//
1079/// Backend-specific internal types.
1080///
1081/// These types are implementation details of the transport backends.
1082/// Most users should use the high-level APIs (`Executor`, etc.)
1083/// instead of these types directly.
1084///
1085/// The `Rmw*` type aliases resolve to whichever backend is active at compile time,
1086/// providing a backend-agnostic way to reference concrete transport types.
1087pub mod internals {
1088 // RFC-0071 D12 (phase-506 W1, issue 1789) — the untyped action machinery the
1089 // C and C++ shims drive, out of the top-level user API.
1090 #[cfg(feature = "rmw-cffi")]
1091 pub use nros_node::{
1092 ActionClientCore, ActionServerCore, ActionServerRawHandle, RawActionClientSpec,
1093 RawActionServerSpec,
1094 };
1095
1096 // ── Backend-agnostic type aliases ────────────────────────────────────
1097 // These resolve to the concrete types of the active RMW backend.
1098 // Today the only exposed backend at this layer is the cffi shim.
1099
1100 #[cfg(feature = "rmw-cffi")]
1101 pub type RmwSession = nros_rmw_cffi::CffiSession;
1102 #[cfg(feature = "rmw-cffi")]
1103 pub type RmwPublisher = nros_rmw_cffi::CffiPublisher;
1104 #[cfg(feature = "rmw-cffi")]
1105 pub type RmwSubscriber = nros_rmw_cffi::CffiSubscription;
1106 #[cfg(feature = "rmw-cffi")]
1107 pub type RmwServiceServer = nros_rmw_cffi::CffiService;
1108 #[cfg(feature = "rmw-cffi")]
1109 pub type RmwServiceClient = nros_rmw_cffi::CffiClient;
1110
1111 /// Phase 124.A — zero-copy publisher slot type. Lives in the
1112 /// `internals` module so `nros-c` can construct + transmute the
1113 /// lifetime when boxing the slot for the C-side `_loan` /
1114 /// `_commit` / `_discard` token plumbing.
1115 #[cfg(all(feature = "rmw-cffi", feature = "lending"))]
1116 pub type RmwSlot<'a> = nros_rmw_cffi::CffiSlot<'a>;
1117
1118 /// Phase 124.A — zero-copy subscriber view type.
1119 #[cfg(all(feature = "rmw-cffi", feature = "lending"))]
1120 pub type RmwView<'a> = nros_rmw_cffi::CffiView<'a>;
1121
1122 /// Open a new middleware session.
1123 ///
1124 /// Wraps the backend-specific session constructor behind a common signature.
1125 /// Used by the C API (`nros-c`); Rust users should prefer `Executor::open()`.
1126 ///
1127 /// Phase 156 — takes an explicit primary backend by name, mirroring what
1128 /// `Executor::open` does for Rust callers. Without it, C bridges built with
1129 /// two linked backends (e.g. xrce + dds) get whichever ctor fires first —
1130 /// non-deterministic across link orderings, and often the wrong backend for
1131 /// the bridge's intended primary.
1132 ///
1133 /// Issue 1050 defect (3) — `rmw` is the RESOLVED selector, not a second
1134 /// reading of the environment. This function used to consult
1135 /// `rmw_selector()` itself, which made it the tree's second answer to "which
1136 /// backend?" and blind to every rung but the environment: a C image with a
1137 /// BAKED selector had it discarded here. The caller resolves the whole
1138 /// ladder (`env > baked > none`) through `ExecutorConfig` and passes the
1139 /// result; `None` means the registry must hold exactly one backend, which
1140 /// `nros_rmw_cffi::get_vtable` now enforces rather than assumes.
1141 #[cfg(feature = "rmw-cffi")]
1142 pub fn open_session(
1143 locator: &str,
1144 mode: nros_rmw::SessionMode,
1145 domain_id: u32,
1146 node_name: &str,
1147 rmw: Option<&str>,
1148 ) -> Result<RmwSession, nros_rmw::TransportError> {
1149 // Phase 249 P4b.1 — every linked backend self-registered via
1150 // its `.init_array` ctor before `main` (RFC-0042 §D3.3); no
1151 // runtime section walk.
1152
1153 // RFC-0109 D8 — the image's baked session properties, as
1154 // `Executor::open` hands them (`nros_node::with_session_properties`).
1155 nros_node::with_session_properties(&[], |properties| {
1156 let config = nros_rmw::RmwConfig {
1157 locator,
1158 mode,
1159 domain_id,
1160 node_name,
1161 namespace: "",
1162 properties,
1163 };
1164 open_with(rmw, &config)
1165 })
1166 }
1167
1168 #[cfg(feature = "rmw-cffi")]
1169 fn open_with(
1170 rmw: Option<&str>,
1171 config: &nros_rmw::RmwConfig<'_>,
1172 ) -> Result<RmwSession, nros_rmw::TransportError> {
1173 use nros_rmw::Rmw;
1174 // Phase 155.B — propagate the real `TransportError` instead of
1175 // collapsing every backend failure to `ConnectionFailed`. The
1176 // C-side `rclc_support_init` decodes the variant into a
1177 // specific `RCLC_RET_*` code so "init -> -X" tells the user
1178 // which precondition the backend rejected.
1179 // issue 1050 defect (3) — the selector ARRIVES here now. It used to be
1180 // read from `$NROS_RMW` on the spot, which is why a baked one could not
1181 // reach this path: the reader knew about one rung and the resolver knew
1182 // about two. The environment still wins; it wins in `ExecutorConfig`,
1183 // where every other field's precedence is decided, instead of here.
1184 if let Some(name) = rmw {
1185 return nros_rmw_cffi::CffiRmw::open_with_rmw(name, config);
1186 }
1187 nros_rmw_cffi::CffiRmw.open(config)
1188 }
1189
1190 /// Drive middleware I/O for pull-based backends.
1191 ///
1192 /// Delegates to [`Session::drive_io()`](nros_rmw::Session::drive_io),
1193 /// which each backend implements appropriately (no-op for push-based,
1194 /// poll for pull-based).
1195 ///
1196 /// Used by the C API executor before polling handles.
1197 #[cfg(feature = "rmw-cffi")]
1198 pub fn drive_session_io(session: &mut RmwSession, timeout_ms: i32) {
1199 use nros_rmw::Session;
1200 let _ = session.drive_io(timeout_ms);
1201 }
1202}
1203
1204// Re-export types that don't depend on RMW (always available)
1205pub use nros_node::{
1206 BOOT_SET_DOMAIN, BOOT_SET_LOCATOR, BOOT_SET_NAMESPACE, BOOT_SET_NODE_NAME, BakedBootConfig,
1207 BootConfig, BootConfigError, DOMAIN_ID_EXPLICIT_ZERO_C_ABI, DOMAIN_ID_MAX, DeferNotify,
1208 ExecutorConfig, ExecutorSemantics, GuardCondition, HandleId, HandleSet, InvocationMode,
1209 NROS_BOOT_CONFIG_MAGIC, NROS_BOOT_CONFIG_VERSION, RawCancelCallback, RawGoalCallback,
1210 RawServiceCallback, RawSubscriptionCallback, ReadinessSnapshot, ShutdownCallbackFn,
1211 ShutdownCallbackHandle, ShutdownPhase, SpinOnceResult, SpinOptions, SpinPeriodPollingResult,
1212 Trigger, baked_domain_from_c_abi,
1213};
1214
1215// ---------------------------------------------------------------------------
1216// The error vocabulary (issue 0783). Both re-exports are unconditional.
1217//
1218// Every fallible call in the Rust user API returns `NodeError`, and
1219// `NodeError::Transport(TransportError)` is its most common variant — so
1220// handling one means naming both. They used to be exported from two unrelated
1221// blocks (`NodeError` with the boot/executor types, `TransportError` with the
1222// transport traits), which is a discoverability nit rather than a capability
1223// gap: both were always reachable, and `prelude` already listed them together.
1224// Nothing here changes what `nros::` exports.
1225//
1226// There is deliberately NO numeric code type beside them. A Rust caller matches
1227// a flat enum; the numeric vocabulary is the C ABI's `rcl_ret_t` /
1228// `RCLC_RET_*` (0, -1..-16), which is its own space and not `rcl_ret_t`'s.
1229// nros-core carried an `rcl_ret_t` mirror (`RclReturnCode`) and an error that
1230// wrapped it (~~`NanoRosError`~~); neither was ever reachable from this facade and
1231// neither had a producer, so issue 0783 deleted them rather than exporting a
1232// type a user could name and never receive. RFC-0036's Errors row now describes
1233// what these two are.
1234// ---------------------------------------------------------------------------
1235/// phase-436 W7 — installing a port's park primitive, with the wake-object
1236/// constraint stated once. Board entries reach it through this facade because
1237/// that is the dependency they already carry.
1238///
1239/// Gated like every other `executor::` re-export here, and it has to be: the
1240/// module itself is `#[cfg(any(has_rmw, test))]`, and `has_rmw` is emitted by
1241/// `nros-node`'s build script exactly when `CARGO_FEATURE_RMW_CFFI` is set. An
1242/// ungated `pub use` therefore names a module that does not exist wherever no
1243/// RMW seam is compiled in — which is not a hypothetical configuration but the
1244/// one `nros build --metadata-mode` uses, where it is `error[E0432]:
1245/// unresolved import` before the harness reaches a single component.
1246#[cfg(feature = "rmw-cffi")]
1247pub use nros_node::executor::port_park;
1248
1249/// phase-436 B2 — the other half of the seam, for the boards that now use it.
1250///
1251/// `port_park` above is what a port hands the executor to WAIT with;
1252/// these are what it hands the executor to be told WHEN. A board registering
1253/// a deadline source receives a [`WakeSourceId`] and reads it back out of
1254/// `Executor::last_park()`, so without this re-export it could match on
1255/// neither.
1256#[cfg(feature = "rmw-cffi")]
1257pub use nros_node::executor::{MAX_WAKE_SOURCES, NextDeadlineFn, WakeSourceId};
1258
1259/// issue 1571 — the spawned tiers' executor backing. `nros::main!` emits one
1260/// `TierExecutorBacking` static per tiered entry and hands its slots to the
1261/// board's `run_tiers`, which refuses a short block through
1262/// `check_tier_executor_backing` and opens each spawned tier with
1263/// `Executor::open_with_session_slot`. Gated like the module it names
1264/// (`has_rmw` + `alloc` in nros-node).
1265#[cfg(all(feature = "rmw-cffi", feature = "alloc"))]
1266pub use nros_node::executor::{
1267 EXECUTOR_BACKING_DEFAULT_U64S, TierBackingShort, TierExecutorBacking, TierExecutorBackingSlot,
1268 TierSetupGate, TierSetupGuard, check_tier_executor_backing,
1269};
1270/// issue 1598 — each spawned tier's TASK memory (stack + control block):
1271/// `nros::main!` emits one `TierTaskMemory` static per spawned tier, sized from
1272/// its `stack_bytes`, and hands the `TierTaskMemorySet` rows to a board whose
1273/// tier runner creates the task over them (FreeRTOS `xTaskCreateStatic`).
1274#[cfg(all(feature = "rmw-cffi", feature = "alloc"))]
1275pub use nros_node::executor::{
1276 TIER_TASK_TCB_U64S, TierTaskMemory, TierTaskMemoryRaw, TierTaskMemorySet,
1277};
1278
1279/// phase-509 P7 (RFC-0108 D8) -- which level an executor is, and whether every
1280/// level of the image is alive: what a supervisor that owns a route per level
1281/// or feeds a watchdog reads, and what a board's tier runner states (the level
1282/// count, and each tier's level by [`tier_level_by`]). Plain atomics in
1283/// nros-node, so ungated.
1284pub use nros_node::executor::{
1285 MAX_LEVELS, every_level_alive, level_count, level_last_spin_us, level_spins, set_level_count,
1286 take_every_level_alive, tier_level, tier_level_by,
1287};
1288
1289pub use nros_node::NodeError;
1290pub use nros_rmw::TransportError;
1291
1292// RFC-0052 / phase-296 W3b — on-target contract-monitor types. Baked
1293// `system_monitors.rs` uses the fully-qualified `::nros_node::executor::
1294// monitor::*` path; this re-export lets hand-written entries and fixtures
1295// reach the same types through the `nros` umbrella (they install the
1296// tables via `Executor::set_monitor_table` / `set_age_table` and drain
1297// with `drain_violations`). The monitor module is `has_rmw`-gated in
1298// nros-node (it names entity types), so mirror that with `rmw-cffi`.
1299#[cfg(feature = "rmw-cffi")]
1300pub mod monitor {
1301 pub use nros_node::executor::monitor::{
1302 AgeMonitorSpec, HazardCell, HazardMonitorRef, HazardMonitorSpec, HazardSpec,
1303 MAX_AGE_MONITORS, MAX_MONITORS, MAX_VIOLATIONS, MECHANISM_EXECUTOR, MECHANISM_TIMER,
1304 ModeDeadline, ModeTables, ModeTablesMismatch, MonitorArming, MonitorSpec, MonitorTableFull,
1305 NO_MODE, NO_SINK, PubMonitorCell, REACTION_NONE, RULE_IDS, RUNG_ROLE_FLOOR, RUNG_ROLE_RUNG,
1306 RUNG_ROLE_WINDOW, ReactionEvent, ReactionHookFn, RouteWatch, RungSpec, SubMonitorCell,
1307 Violation, check_table_capacity, fqn_hash, request_mode, request_monitor_arming,
1308 requested_mode, route_next_due_us, rule_code, rule_name,
1309 };
1310}
1311
1312// Issues 1635 / 1676 — the contract-monitor install and the `/diagnostics`
1313// reporter, ONE implementation: `nros::main!` calls `install_contract_monitors`
1314// on every executor it builds, and `nros-cpp`'s `nros_cpp_install_monitors`
1315// arms the same `DiagSink`.
1316#[cfg(feature = "rmw-cffi")]
1317pub mod contract;
1318
1319// Re-export RMW-dependent types (require an active transport backend)
1320#[cfg(feature = "rmw-cffi")]
1321pub use nros_node::{
1322 ActionClient, ActionServer, ActionServerHandle, ActiveGoal, CompletedGoal, EmbeddedPublisher,
1323 EmbeddedRawPublisher, EmbeddedServiceClient, EmbeddedServiceServer, Executor, ExecutorSizing,
1324 FeedbackStream, GoalFeedbackStream, LoanError, Node, Promise, PublishLoan, RawActiveGoal,
1325 RawSubscription, RecvView, SessionHandle, SessionSpec, Subscription, action_channel_type,
1326};
1327
1328// phase-271 (issue #110) — per-entry executor sizing helper: the orchestration
1329// codegen's ~~`build_executor`~~ sizes its backing to the system's callback count
1330// via `nros::arena_size_for(CALLBACK_COUNT)` + `ExecutorSizing`, replacing the
1331// workspace-global `NROS_EXECUTOR_MAX_CBS`.
1332#[cfg(feature = "rmw-cffi")]
1333pub use nros_node::config::arena_size_for;
1334
1335/// The configured default receive-buffer size (`NROS_SUBSCRIPTION_BUFFER_SIZE`).
1336///
1337/// NOT a fallback for a missing bound any more (phase-403 W0): [`rx_buffer_for!`]
1338/// used to expand to this for an unbounded type and now refuses to compile
1339/// instead. It stays public, and stays the default, for the paths that have no
1340/// `M` to ask rather than a bound they declined to use -- `create_subscription_raw`
1341/// and the RFC-0043 type-name-string subscriptions, the service / action / TX
1342/// buffer defaults, the `pubsub_entry` term of the arena derivation in
1343/// `nros-node/build.rs`, and its weld to the C API's `MESSAGE_BUFFER_SIZE`.
1344/// Whether the arena derivation should stop leaning on it is a phase-403 W5
1345/// question.
1346///
1347/// Re-exported here (rather than reached through `nros_node`) because a macro
1348/// expands at the CALLER, where `nros_node` may not be a dependency at all.
1349pub use nros_node::config::DEFAULT_RX_BUF_SIZE;
1350
1351/// Phase 392 W3b — the bound behind [`rx_buffer_for!`]. Not part of the stable
1352/// surface; call the macro.
1353///
1354/// Public only because a `macro_rules!` body is expanded in the caller's crate
1355/// and can reach nothing private. `#[doc(hidden)]` and `__`-prefixed for the
1356/// same reason every other macro-support item in this crate is.
1357#[doc(hidden)]
1358pub const fn __rx_bound<M: nros_serdes::schema::Message>() -> Option<usize> {
1359 nros_serdes::size::max_serialized_bound::<M>()
1360}
1361
1362// Phase 173.5 — board config traits. `BoardConfig` (read locator /
1363// domain). ~~`BoardTransportConfig`~~ was removed with its dead
1364// setters (issue 1064); the live path is the deploy overlay.
1365pub use nros_platform::BoardConfig;
1366
1367// Phase 216.A.1 — `DispatchStrategy` enum. User-visible at
1368// `nros::DispatchStrategy`; the canonical home is `nros_platform::
1369// board::dispatch` so the C ABI symbol the `nros::node!()` macro emits
1370// (`__nros_node_<pkg>_dispatch_strategy() -> u8`) lives next to the
1371// other board-side trampolines.
1372pub use nros_platform::DispatchStrategy;
1373
1374/// Implementation detail — used by `nros::node!()` macro expansion.
1375///
1376/// Re-exports `nros_platform` so the macro's emitted trampoline can
1377/// reference `RuntimeCtx` / `RuntimeError` / the `Node*Fn`
1378/// fn-pointer aliases without forcing every consumer Node pkg's
1379/// `Cargo.toml` to carry an explicit `nros-platform` dep on top of
1380/// `nros`. Phase 212.M-F.13 path (b).
1381///
1382/// Not part of the public API — paths under this module may change at
1383/// any time. End users should depend on `nros` alone and invoke
1384/// `nros::node!()`; the macro routes through here automatically.
1385#[doc(hidden)]
1386pub mod __macro_support {
1387 /// RFC-0109 D8 — the baked `rmw/zenoh.json5` session-property table.
1388 pub use ::nros_node::{SessionProperty, set_session_properties};
1389
1390 /// Issue 2051 — the table `nros build` rendered for a Zephyr Rust west
1391 /// application (`NROS_SESSION_PROPERTIES_RS`), for the entry macro that
1392 /// cannot read the text at expansion ([`crate::zephyr_component_main!`]).
1393 /// Empty when the build names none.
1394 pub mod baked_session {
1395 include!(concat!(env!("OUT_DIR"), "/baked_session_properties.rs"));
1396 }
1397 pub use ::nros_platform;
1398
1399 /// phase-314 — whether THIS `nros` build carries the parameter services.
1400 ///
1401 /// The `nros::main!` expansion const-asserts it when the system declares
1402 /// `[param_services]`. A cfg in the entry crate cannot see this: the
1403 /// feature belongs to `nros`, and the entry enables it through its
1404 /// dependency, so only `nros` itself can report the answer.
1405 ///
1406 /// Without the assert the mismatch is SILENT — `apply_param_services` is a
1407 /// no-op, the build succeeds, the image boots, and `ros2 param list`
1408 /// returns nothing.
1409 pub const PARAM_SERVICES_ENABLED: bool = cfg!(feature = "param-services");
1410
1411 /// phase-461 W6 — whether THIS `nros` build carries the parameter STORE
1412 /// (`param-store`, which `param-services` implies). A system declaring the
1413 /// `params` axis const-asserts it, for the reason above: without the
1414 /// feature `apply_param_store` is a no-op and every `declare_parameter`
1415 /// is refused at boot instead of at compile time.
1416 pub const PARAM_STORE_ENABLED: bool = cfg!(feature = "param-store");
1417
1418 /// Issue 1766 — the same answer for the other capability axes a leaf
1419 /// can declare in `[system] features`. A Form-1 `nros::main!()`
1420 /// const-asserts the flag of every declared axis
1421 /// (`nros_orchestration_ir::leaf_capabilities::LeafAxis::compiled_flag`),
1422 /// because without the feature the axis is compiled out and the
1423 /// declaration would be dropped in silence.
1424 pub const LIFECYCLE_SERVICES_ENABLED: bool = cfg!(feature = "lifecycle-services");
1425 /// See [`LIFECYCLE_SERVICES_ENABLED`].
1426 pub const SAFETY_E2E_ENABLED: bool = cfg!(feature = "safety-e2e");
1427 /// See [`LIFECYCLE_SERVICES_ENABLED`].
1428 pub const ROSOUT_ENABLED: bool = cfg!(feature = "rosout");
1429
1430 /// Issue 0257 — the build-time executor callback-table size
1431 /// (`NROS_EXECUTOR_MAX_CBS`, default 4). Re-exported so the `nros::main!`
1432 /// expansion can `const`-assert the model's entity count against the
1433 /// capacity that ACTUALLY compiles in, instead of letting the image boot
1434 /// and die on `create_timer (code=-6 Full)`.
1435 pub use ::nros_node::config::MAX_CBS as EXECUTOR_MAX_CBS;
1436}
1437
1438// Phase 110.B / 110.G — scheduling-context API surface. Consumers
1439// of the Phase 110 cyclic / TT scheduler need these types to
1440// describe schedules and bind handles; re-exporting them here
1441// keeps user code free of `nros_node::executor::sched_context`
1442// path noise. Gated on `rmw-cffi`: the source module is
1443// `#[cfg(any(has_rmw, test))]` in nros-node, so it only exists once
1444// an RMW backend is linked (matches the re-export block above).
1445#[cfg(feature = "rmw-cffi")]
1446pub use nros_node::executor::sched_context::{
1447 DeadlineAction, DeadlinePolicy, OptUs, Priority, SchedClass, SchedContext, SchedContextId,
1448 TimeTriggeredSchedule, TimeTriggeredScheduleError, TimeTriggeredWindow,
1449};
1450
1451#[cfg(all(feature = "alloc", feature = "rmw-cffi"))]
1452pub use nros_node::SpinPeriodResult;
1453
1454// Re-export service types
1455pub use nros_core::{ServiceClient, ServiceServer};
1456
1457// Re-export action types.
1458//
1459// issue 0796 — `CancelResponse` (the per-goal Reject/Accept decision) and
1460// `CancelResponseCode` (the `action_msgs/srv/CancelGoal` RPC status) are two
1461// concepts that shared one name until the split. Both are exported: without
1462// `CancelResponseCode` here, `ActionClient::cancel_goal`'s `Promise<CancelResponseCode>`
1463// could not be NAMED from `nros::` alone.
1464pub use nros_core::{
1465 CancelResponse, CancelResponseCode, GoalInfo, GoalResponse, GoalStatusCode, GoalStatusStamped,
1466 GoalUuid, RosAction,
1467};
1468// phase-515 R8 -- the pre-rename names, deprecated for one release.
1469#[allow(deprecated)]
1470pub use nros_core::{CancelReturnCode, GoalId, GoalStatus};
1471
1472// Re-export lifecycle types (always available, no_std compatible)
1473pub use nros_core::{LifecycleState, LifecycleTransition, TransitionResult};
1474pub use nros_node::{LifecycleCallbackFn, LifecycleError, LifecyclePollingNode};
1475
1476/// Re-export of the full lifecycle module so examples can reach
1477/// `LifecycleCallbackSlot`, `LifecyclePollingNodeCtx`, etc.
1478pub mod lifecycle {
1479 pub use nros_core::lifecycle::{LifecycleState, LifecycleTransition, TransitionResult};
1480 pub use nros_node::lifecycle::*;
1481}
1482
1483// Phase 128.G — bridge surface re-exports. Gated behind the
1484// `bridge` / `config` umbrella features so single-backend builds
1485// don't pull in `nros-bridge` (or, for `config`, the TOML stack).
1486#[cfg(feature = "bridge")]
1487pub use nros_bridge as bridge;
1488
1489#[cfg(feature = "config")]
1490pub use nros_bridge::run_from_config;
1491
1492// Re-export parameter types.
1493//
1494// phase-382 W2' — `ParameterStorage` / `ParameterTable` are here because the
1495// store's slots are CALLER-OWNED: anyone constructing a `ParameterServer`
1496// outside an executor has to place the storage and lend it.
1497// phase-426 W1 — `NodeKey` is here because every per-parameter call names the
1498// node it is about; a consumer holding a `ParameterServer` needs it to say so.
1499// phase-417 W4.a — the rest of the parameter vocabulary. Five ledger rows
1500// (`rust:ParameterRange`, `rust:ParameterRanges`, `rust:Parameters`,
1501// `rust:Node::use_undeclared_parameters`, `rust:RmwParameterConversionError`)
1502// were not missing capabilities: `nros_params` had every one of them and this
1503// list stopped at ten names, so a user of the facade could build a range
1504// through the builder's scalar arguments and never NAME the type it produced.
1505// An unexported type is a capability a user cannot write down.
1506/// Deprecated (phase-515): the store answers `Result<(), ParameterError>`.
1507#[allow(deprecated)]
1508pub use nros_params::SetParameterResult;
1509#[allow(deprecated)]
1510pub use nros_params::UndeclaredParameters;
1511pub use nros_params::{
1512 DeclarationError, FloatingPointRange, IntegerRange, MAX_ON_SET_CALLBACKS, MandatoryParameter,
1513 NodeKey, OnSetContext, OnSetParameterFn, OnSetParameterHandle, OptionalParameter, Parameter,
1514 ParameterBuilder, ParameterDescriptor, ParameterError, ParameterRange, ParameterServer,
1515 ParameterStorage, ParameterTable, ParameterType, ParameterValue, ParameterVariant, Parameters,
1516 ReadOnlyParameter,
1517};
1518// The wire-side half: why an `rcl_interfaces` parameter value could not be
1519// turned into a native one. The `~/set_parameters` handler already puts
1520// `reason()` into the `SetParametersResult`; without the type a user could read
1521// the sentence and not match on the cause.
1522// Gated like the module it comes from: `parameter_services` is the six
1523// `rcl_interfaces/srv/*` servers, and an image without them has no wire side
1524// for this error to describe.
1525// Gated like the module it comes from: `parameter_services` is
1526// `#[cfg(all(feature = "param-services", any(has_rmw, test)))]` in nros-node,
1527// so it exists only once an RMW backend is linked — the same pairing the
1528// `sched_context` re-export above uses.
1529#[cfg(all(feature = "param-services", feature = "rmw-cffi"))]
1530pub use nros_node::parameter_services::RmwParameterConversionError;
1531#[cfg(all(feature = "param-services", feature = "rmw-cffi"))]
1532#[allow(deprecated)]
1533pub use nros_node::parameter_services::ValueConversionError;
1534/// Prelude module for convenient imports
1535///
1536/// Import everything you need with a single statement:
1537/// ```
1538/// use nros::prelude::*;
1539/// ```
1540/// phase-379 W5 — the RTOS machinery, named explicitly.
1541///
1542/// The second tier of the two-tier surface. `nros::prelude` is the API a ported
1543/// ROS 2 node uses; everything here exists because the target is an RTOS, has no
1544/// correspondent in rclrs/rclcpp/rclc, and a ROS 2 developer reading a node
1545/// should not have to step over it.
1546///
1547/// Nothing moved out of `nros::` — these are re-exports, and every name is still
1548/// reachable at its old path. What changed is that they are no longer dragged in
1549/// by `use nros::prelude::*`.
1550///
1551/// The membership rule is mechanical rather than taste: a name belongs in the
1552/// PRELUDE iff the parity ledger gives it a non-`extension` verdict — i.e. it
1553/// corresponds to something in rclrs, rclcpp or rclc. Names with no
1554/// correspondent belong here, unless they are load-bearing for startup
1555/// (`ExecutorConfig`, `SpinOptions`), which is an argued allow-list rather than
1556/// an exception anyone may grow.
1557pub mod embedded {
1558 // Wire encoding. A node publishes typed messages; these are for code that
1559 // handles bytes, which upstream hides entirely.
1560 pub use crate::{CdrReader, CdrWriter};
1561
1562 // Handle bookkeeping — the static tables that replace an allocator.
1563 #[cfg(feature = "rmw-cffi")]
1564 pub use crate::{HandleId, HandleSet, InvocationMode};
1565
1566 // Component/runtime plumbing, and the source-metadata capture the
1567 // orchestration layer records. None of it appears in a ported node.
1568 pub use crate::MetadataRecorder;
1569
1570 // Entity REGISTRATION vocabulary. A ported node writes
1571 // `create_publisher(...)`; these are what the declaration macros and the
1572 // orchestration layer use to describe what was created.
1573 #[cfg(feature = "rmw-cffi")]
1574 pub use crate::__macro::{record_node_metadata, register_node};
1575 #[cfg(feature = "rmw-cffi")]
1576 pub use crate::CallbackEffects;
1577 pub use crate::{
1578 ActionTag, Callback, CallbackEffectKind, EntityKind, NodeRuntime, ServiceTag,
1579 SourceLocationMetadata, SourceNameKind, SubscriptionTag,
1580 };
1581 // Gated where the root gates it — the export follows the capability, not
1582 // the tier.
1583 #[cfg(feature = "alloc")]
1584 pub use crate::SourceMetadataExport;
1585}
1586
1587pub mod prelude {
1588 // phase-379 W5 — the RTOS machinery moved to `nros::embedded`. Removed
1589 // here rather than re-exported from both: a two-tier surface that still
1590 // drags tier two in through the glob is one tier with extra words.
1591
1592 pub use crate::{
1593 Deserialize, Logger, MessageInfo, QoSDurabilityPolicy, QoSHistoryPolicy, QoSProfile,
1594 QoSReliabilityPolicy, RosMessage, RosService, Serialize, ServiceInfo,
1595 };
1596
1597 // phase-417 / issue 0589 — logging, in the glob a node author already
1598 // writes. `Logger` above now means `nros_log::Logger`; these are what
1599 // drives it. Without them the shortest path from `use nros::prelude::*` to
1600 // a printed line was `std::println!`, which kills a Zephyr `native_sim`
1601 // image outright.
1602 //
1603 // The five severity macros now carry rclrs's OWN names (RFC-0089, settled
1604 // 2026-09-04), so a ROS 2 developer meeting one through the glob meets the
1605 // exact spelling their client library has, not a near-miss of it. That is
1606 // the property `check-prelude-tiers` enforces mechanically: a ledger
1607 // `extension` may not sit in the glob.
1608 //
1609 // `Severity`, `get_logger`, `get_or_create_logger`, `register_logger` and
1610 // `nros_trace` are ledger `extension`s -- rclrs has no correspondent for a
1611 // free logger lookup, a registry, or a TRACE severity (it stops at debug).
1612 // They stay behind `nros::logging::` / the crate root, where reaching for
1613 // one is a decision. phase-379 W5's rule, applied to this phase's own work.
1614 //
1615 // The throttle macros ARE here: they are ledger `divergence`s, not
1616 // extensions -- rclrs has the capability under a different decomposition
1617 // (`log_info!(logger.throttle(d), "…")`), so the glob is not introducing a
1618 // name with no upstream counterpart.
1619 #[cfg(all(feature = "alloc", feature = "rmw-cffi"))]
1620 pub use crate::{Component, DeclarativeNode, NodeContext};
1621 #[cfg(feature = "alloc")]
1622 pub use crate::{
1623 NodeActionClient, NodeActionServer, NodeDeclError, NodeParameter, NodePublisher,
1624 NodeResult, NodeServiceClient, NodeServiceServer, NodeSubscription, NodeTimer,
1625 ParameterDefault, node,
1626 };
1627 pub use crate::{
1628 log_debug, log_error, log_fatal, log_info, log_warn, nros_debug_throttle,
1629 nros_debug_throttle_at, nros_error_throttle, nros_error_throttle_at, nros_fatal_throttle,
1630 nros_fatal_throttle_at, nros_info_throttle, nros_info_throttle_at, nros_warn_throttle,
1631 nros_warn_throttle_at,
1632 };
1633
1634 // phase-515 R2 -- `executor.create_node("talker".namespace("/ns"))`.
1635 pub use crate::{IntoNodeOptions, NodeOptions};
1636 // phase-515 R1 -- `node.create_publisher::<M>("chatter".keep_last(10))`.
1637 pub use crate::{IntoPrimitiveOptions, PrimitiveOptions};
1638
1639 // Re-export lifecycle types
1640 pub use crate::{
1641 LifecycleCallbackFn, LifecycleError, LifecyclePollingNode, LifecycleState,
1642 LifecycleTransition, TransitionResult,
1643 };
1644
1645 // Re-export executor config + handle types (always available)
1646 pub use crate::{
1647 ExecutorConfig, GuardCondition, NodeError, SessionMode, SpinOnceResult, SpinOptions,
1648 SpinPeriodPollingResult, TransportError, Trigger,
1649 };
1650
1651 // issue 0687 — `ExecutorConfig::from_env()` is an extension trait now (the
1652 // environment is read at this crate's edge, not in the core), so the
1653 // spelling only works where the trait is in scope. It is in the prelude
1654 // precisely so that the consumers written against the inherent method —
1655 // the native examples, the benches — keep compiling unchanged.
1656 #[cfg(feature = "env")]
1657 pub use crate::ExecutorConfigEnvExt;
1658
1659 // Re-export RMW-dependent executor + handle types
1660 #[cfg(feature = "rmw-cffi")]
1661 pub use crate::{
1662 EmbeddedPublisher, EmbeddedServiceClient, Executor, FeedbackStream, Node, Promise,
1663 Subscription,
1664 };
1665
1666 // Publisher/Subscriber options (topic + QoS).
1667 pub use crate::{PublisherOptions, SubscriptionOptions};
1668
1669 #[cfg(all(feature = "alloc", feature = "rmw-cffi"))]
1670 pub use crate::SpinPeriodResult;
1671
1672 // Re-export parameter types
1673 pub use crate::{ParameterServer, ParameterStorage, ParameterType, ParameterValue};
1674
1675 // Re-export typed parameter API (rclrs-compatible builder pattern)
1676 pub use crate::{
1677 MandatoryParameter, OptionalParameter, ParameterBuilder, ParameterError, ParameterVariant,
1678 ReadOnlyParameter,
1679 };
1680
1681 // Re-export action types
1682 #[allow(deprecated)]
1683 pub use crate::{GoalId, GoalStatus};
1684 pub use crate::{
1685 GoalInfo, GoalResponse, GoalStatusCode, GoalStatusStamped, GoalUuid, RosAction,
1686 };
1687
1688 // Re-export Time, Duration, Clock from core
1689 pub use nros_core::{Clock, ClockType, Duration, Time};
1690
1691 // Re-export timer types
1692 pub use crate::{TimerCallbackFn, TimerDuration, TimerHandle, TimerMode};
1693}
1694
1695/// Derive macros for message types
1696///
1697/// Use these macros to generate message serialization code.
1698/// These macros help you create custom message types that are compatible
1699/// with ROS 2's CDR serialization format.
1700pub mod derive {
1701 #[cfg(feature = "macros")]
1702 pub use nros_macros::RosMessage;
1703}
1704
1705#[cfg(test)]
1706mod tests {
1707 #[test]
1708 fn test_prelude_imports() {
1709 // This test just verifies that the prelude compiles
1710 use crate::prelude::*;
1711
1712 let _ = QoSProfile::BEST_EFFORT;
1713 }
1714
1715 /// The component trait, its registration context and the result type
1716 /// resolve under the names phase-483 W2/W3 gave them: `Component` (the
1717 /// trait, which phase-212 had named `Node`) and a `NodeContext` with no
1718 /// runtime parameter.
1719 #[cfg(feature = "rmw-cffi")]
1720 #[test]
1721 fn node_context_types_resolve() {
1722 fn _take_node_ctx<N: crate::Component>(_: &mut crate::NodeContext<'_>) {}
1723 let _: crate::NodeResult<()> = Ok(());
1724 }
1725}
1726
1727// ---------------------------------------------------------------------------
1728// phase-361 W8.e / issue 0594 — capabilities REQUIRE the heap / the standard
1729// library, they do not enable it. Turning `alloc` or `std` on for the user
1730// silently changes what their firmware image is; naming the feature they must
1731// add does not.
1732// ---------------------------------------------------------------------------
1733// issue 0687 follow-up — `alloc`, not `std`. The reason recorded here ("writes
1734// a file and exits") was never this crate's: the file write is `nros-cpp`'s
1735// `metadata_hooks`. What `metadata_mode.rs` actually needed was a `Sync`
1736// global, and it named `std::sync::Mutex` for it; on the portable mutex the
1737// capability is `String` + `format!` + a lock, i.e. the heap and nothing more.
1738#[cfg(all(feature = "metadata-mode", not(feature = "alloc")))]
1739compile_error!(
1740 "`metadata-mode` records into a heap-allocated global: add \"alloc\" to this crate's features"
1741);
1742// phase-359 W10 stage B — `env` needs a PROCESS, not the standard library:
1743// its reader is the C library's `getenv` (`src/env.rs`). It needs the heap
1744// for `Context`'s owned strings and the `--params-file` read.
1745#[cfg(all(feature = "env", not(target_os = "linux")))]
1746compile_error!(
1747 "`env` reads the process environment through the C library, which this target does not have: a target image takes its boot config from `system.toml` (baked), so drop \"env\""
1748);
1749#[cfg(all(feature = "env", not(feature = "alloc")))]
1750compile_error!("`env` builds an owned `Context`: add \"alloc\" to this crate's features");
1751
1752// phase-379 W5, revised by phase-428 W10 — the crate-level `QOS_PROFILE_*`
1753// presets (rclrs parity) and the older `nros::qos::*` module are two spellings
1754// of the same profiles. BOTH now alias `QoSProfile`'s associated consts; until
1755// W10 the second restated them as struct literals, and these tests were the only
1756// thing holding the two copies together.
1757//
1758// They are kept, and what they assert has changed meaning. They no longer catch
1759// a hand-mirror drifting (there is no longer a hand mirror to drift — issues
1760// 0088 / 0160 / 0245). They catch a REINTRODUCTION: the moment someone writes a
1761// literal into either surface instead of aliasing, a value can differ and one of
1762// these fails. That is cheap enough to keep and is the only mechanism standing
1763// between this crate and the arrangement W10 removed.
1764//
1765// What they do NOT check, deliberately, is whether the SSoT matches UPSTREAM.
1766// Two aliases of a wrong constant agree perfectly — which is exactly the state
1767// `QOS_PROFILE_PARAMETER_EVENTS` was in (`KeepAll, 0` against upstream's
1768// `KEEP_LAST, 1000`). That assertion belongs beside the associated consts in
1769// `nros-rmw/src/traits.rs`, not here.
1770#[cfg(test)]
1771mod qos_preset_parity {
1772 use super::*;
1773
1774 #[test]
1775 fn crate_level_presets_alias_the_associated_consts() {
1776 assert_eq!(QOS_PROFILE_DEFAULT, QoSProfile::QOS_PROFILE_DEFAULT);
1777 assert_eq!(QOS_PROFILE_SENSOR_DATA, QoSProfile::QOS_PROFILE_SENSOR_DATA);
1778 assert_eq!(QOS_PROFILE_PARAMETERS, QoSProfile::QOS_PROFILE_PARAMETERS);
1779 assert_eq!(
1780 QOS_PROFILE_ACTION_STATUS_DEFAULT,
1781 QoSProfile::QOS_PROFILE_ACTION_STATUS_DEFAULT
1782 );
1783 }
1784
1785 /// `qos::*` was a SEPARATE hand-written copy until phase-428 W10; all five
1786 /// names alias the associated consts now, so this passes by construction.
1787 /// It stays as the tripwire for re-introducing a literal — see the module
1788 /// comment above.
1789 #[test]
1790 fn qos_module_agrees_with_the_presets() {
1791 // issue 0829 FIXED — SYSTEM_DEFAULT was the first to become an alias.
1792 // It is here rather than in its own pinned test because the two copies
1793 // no longer say anything a copy could get wrong: both alias the one
1794 // associated const, which is all sentinel.
1795 assert_eq!(
1796 qos::SYSTEM_DEFAULT,
1797 QOS_PROFILE_SYSTEM_DEFAULT,
1798 "qos::SYSTEM_DEFAULT drifted"
1799 );
1800 assert_eq!(qos::DEFAULT, QOS_PROFILE_DEFAULT, "qos::DEFAULT drifted");
1801 assert_eq!(
1802 qos::SENSOR_DATA,
1803 QOS_PROFILE_SENSOR_DATA,
1804 "qos::SENSOR_DATA drifted"
1805 );
1806 assert_eq!(
1807 qos::SERVICES_DEFAULT,
1808 QOS_PROFILE_SERVICES_DEFAULT,
1809 "qos::SERVICES_DEFAULT drifted"
1810 );
1811 assert_eq!(
1812 qos::PARAMETERS,
1813 QOS_PROFILE_PARAMETERS,
1814 "qos::PARAMETERS drifted"
1815 );
1816 }
1817
1818 /// issue 0829, RESOLVED — this test used to PIN the divergence, asserting
1819 /// depth 10 on the façade side and depth 1 on the `nros-rmw` side because
1820 /// neither was obviously the live one. Both numbers are gone, and the
1821 /// replacement is not "we picked one": no concrete depth can be right,
1822 /// because the two reference RMWs resolve the same sentinel differently
1823 /// (`rmw_cyclonedds_cpp` → `KEEP_LAST, 1`; `rmw_zenoh_cpp` →
1824 /// `RMW_ZENOH_DEFAULT_HISTORY_DEPTH`, 42).
1825 ///
1826 /// So what is asserted now is the SHAPE: `SYSTEM_DEFAULT` states nothing,
1827 /// on every field. That is what makes it different from `DEFAULT` — the
1828 /// two being byte-identical was the older defect, and asserting they
1829 /// DIFFER is what keeps anyone from quietly aliasing them again.
1830 #[test]
1831 fn system_default_states_nothing_on_every_field() {
1832 use nros_rmw::{
1833 DEPTH_SYSTEM_DEFAULT, QoSDurabilityPolicy, QoSHistoryPolicy, QoSLivelinessPolicy,
1834 QoSReliabilityPolicy,
1835 };
1836 let sd = QOS_PROFILE_SYSTEM_DEFAULT;
1837 assert_eq!(sd.reliability, QoSReliabilityPolicy::SystemDefault);
1838 assert_eq!(sd.durability, QoSDurabilityPolicy::SystemDefault);
1839 assert_eq!(sd.history, QoSHistoryPolicy::SystemDefault);
1840 assert_eq!(sd.depth, DEPTH_SYSTEM_DEFAULT);
1841 // `None` IS the liveliness sentinel — it lowers to
1842 // `NROS_RMW_LIVELINESS_SYSTEM_DEFAULT` (0), the two having collapsed
1843 // onto one value in phase-376 W5/B2.
1844 assert_eq!(sd.liveliness_kind, QoSLivelinessPolicy::None);
1845 assert!(sd.has_unresolved_system_default());
1846
1847 // The whole point of the name: it is NOT a synonym for DEFAULT.
1848 assert_ne!(
1849 sd, QOS_PROFILE_DEFAULT,
1850 "SYSTEM_DEFAULT aliased DEFAULT again — see issue 0829"
1851 );
1852 assert!(!QOS_PROFILE_DEFAULT.has_unresolved_system_default());
1853 }
1854}