macro_rules! rx_buffer_for {
($msg:ty) => { ... };
}Expand description
Route this image’s panics to its platform — phase-366 W5.c / RFC-0077.
Emits the #[panic_handler] for an embedded image, forwarding the message
to nros_platform_panic so a Rust panic ends the same way a C precondition
failure or a C++ terminate does. What that ending IS belongs to the port:
k_panic() on Zephyr, esp_system_abort() on ESP-IDF, UART-then-exit-QEMU
on the ThreadX RV64 board.
§Why this is a macro you INVOKE, not something nros::main! emits
#[panic_handler] is a singleton of the final artifact, and the image owns
it. Emitting one silently from nros::main!() would collide with every image
that already declares its own — examples/qemu-esp32-baremetal writes
use esp_backtrace as _;, and logging-smoke-freertos-mps2 uses
panic-semihosting with features = ["exit"] so a panic exits QEMU instead
of hanging the test harness. Those images are RIGHT, and an invisible default
would fight them.
So the line is written in the entry, where it can be read, swapped for
use panic_halt as _;, or replaced by a hand-written handler that logs to
NVM and reboots. A default you cannot see is a constraint, not a default.
§Use — main!(panic = …) is the normal way; this is the escape hatch
phase-366 R3. An entry that goes through nros::main!() should say
panic = "platform" (or nothing — that is the default since M5) and let the
macro emit this body. One line, and the build can check it.
#![no_std]
nros::main!(); // ends through `nros_platform_panic`Invoke this macro directly only where no main!() expansion can carry the
item — a hand-rolled no_std binary, or the lib side of a crate whose
crate-type includes staticlib and whose entry macro is
zephyr_component_main! / a board app_main!. It is kept for exactly those:
deleting it would strand the images that cannot use the macro replacing it.
// src/app_main.rs — the .a is a final artifact to rustc, and `main!()` in
// the bin target never reaches it.
nros::panic_to_platform!();Do NOT invoke it in a std image: libstd supplies the lang item there and a
second one does not compile. Do not invoke it alongside use panic_halt as _
or any other provider, for the same reason — that is the duplicate
check-archive-lang-items exists to catch.
Park the core on panic — the halt value of nros::main!(panic = …).
For an image that must not print: no formatting, no allocation, no call out to the platform. Interrupts are masked first so the parked core cannot be woken back into a half-dead system by a timer or a driver ISR still armed from before the panic.
This is a body rather than a re-export of the panic-halt crate so that
choosing it costs the entry no new dependency — main!(panic = "halt") is a
word in a macro the crate already calls, which is the point of the surface.
The behaviour is the same: mask, then spin forever.
Prefer panic = "platform". Halting discards the diagnosis, and every port
implements nros_platform_panic precisely so a dying image can say why.
Phase 392 W3b — the receive-buffer size for a message type, as a constant
that cannot drift from the type.
node.subscription::<PointCloud2>("points")
.rx_buffer::<{ nros::rx_buffer_for!(PointCloud2) }>()
.build(on_cloud)?;.rx_buffer::<N>() has always accepted a number. The problem is where the
number comes from: a literal is correct until someone appends a field to the
message, and then it is silently too small — the sample is received, ACKed
and dropped at the transport, which needs a packet capture to attribute
(report_dropped_take, and the 13.4 KiB Autoware trajectory case). This
expands to the type’s own bound, computed from its schema by phase 380, so
appending a field moves the buffer with it.
Why a macro and not a method. The builder cannot do this for you:
inside impl<M> TypedSubscriptionBuilder<M> the type is a generic
parameter, and on stable Rust a generic parameter may not appear in a const
operation — error: generic parameters may not be used in const operations.
At a call site the type is CONCRETE, which is legal, so the size has to be
named where the type is. That constraint is also why phase-392’s size
classes were “decoupled from codegen” in the first place.
Unbounded types are a BUILD ERROR (phase-403 W0). A type with an
unbounded string/wstring/sequence has no bound, and this refuses to
invent one. It used to expand to DEFAULT_RX_BUF_SIZE; it now fails to
compile, naming both remedies.
Every message type is REQUIRED to carry a derived upper bound, stated in the
.msg (string<=64) or capped in nros-codegen.toml. Phase 380 is explicit
that None means “no bound EXISTS”, never “unknown”, and that a buffer must
not be sized from a fallback. Substituting the configured default was the
violation of that rule; refusing is what it licenses. report_dropped_take
is a backstop for a buffer that is too small, not a licence to pick one.
use nros_serdes::schema::{Field, FieldType, Message};
struct Unbounded;
impl Message for Unbounded {
const TYPE_NAME: &'static str = "test/msg/Unbounded";
const FIELDS: &'static [Field] = &[Field {
name: "s",
ty: FieldType::String,
offset: 0,
}];
}
let _n: usize = nros::rx_buffer_for!(Unbounded);The positive control for that compile_fail, so it cannot pass because the
fixture stopped compiling for an unrelated reason:
use nros_serdes::schema::{Field, FieldType, Message};
struct Bounded;
impl Message for Bounded {
const TYPE_NAME: &'static str = "test/msg/Bounded";
const FIELDS: &'static [Field] = &[Field {
name: "a",
ty: FieldType::Uint64,
offset: 0,
}];
}
let n: usize = nros::rx_buffer_for!(Bounded);
assert!(n > 0);Why the const block. The macro is used in two positions: as a
const-generic argument (.rx_buffer::<{ rx_buffer_for!(M) }>()) and as a
plain expression (let n = rx_buffer_for!(M);). A bare panic! is a build
error in the first and a RUNTIME panic in the second, which would make the
rule depend on where the macro appears. Wrapping the whole match in an inline
const block forces compile-time evaluation in BOTH, so an unbounded type
can never reach a running image.
What the error names. rustc points at this macro invocation, where the
type is written literally, so the TYPE is named. The MEMBER that costs the
bound cannot be in the message: const evaluation does not format, so a
panic! there takes a literal only. The member is named by the codegen
diagnostic for the same type – unbounded_reason in the generated C header
(packs/c/message.h.jinja), which names EVERY member that costs the bound in
one build (phase-403 W0), or nros_serdes::size::visit_unbounded over its
FIELDS.