Skip to main content

honour

Function honour 

Source
pub fn honour(
    type_name: &str,
    topic: &str,
    qos: QoSProfile,
) -> Result<QoSProfile, NodeError>
Expand description

The QoS a Rust subscription actually registers with, once the contract has had its say — phase-454 W13, the Rust half of W10, widened to the two policies by issue 1256.

§Why Rust TAKES the declared value where C and C++ ASSERT it

C++ settles this at the call site, by arity: NROS_SUBSCRIBE(Msg, m, topic) states no QoS, so qos_from_declared BUILDS one from the declaration (“nothing to assert — there is only ever one statement”), and the four-argument form, where the call site did state one, static_asserts that the two agree. C does the same with NROS_ASSERT_DECLARED_*.

Rust has neither half of that seam: there is no nros::subscribe!, and the topic is a runtime &str, so nothing at the call site can be told apart from anything else at compile time — and by the time a QoS reaches a registration, “the caller wrote this” and “a constructor defaulted it” are the same QoSProfile. Node::create_subscription hands QoSProfile::default() — rmw_qos_profile_default, KEEP_LAST(10), RELIABLE, VOLATILE — to the same _with_qos entry point a caller with an opinion reaches, and the declarative road folds both into one EntityMetadata::qos field long before this point.

So the distinction C++ makes is not available here, and the two candidate rules that remain each fail on their own:

  • Check only. Every QoSProfile::default() call site in the tree would be refused the moment its endpoint is declared at anything but the default — which is the entire point of declaring one. A declaration would become a way to stop an image booting.
  • Take, always. A caller who narrowed a queue on purpose (create_subscription_viewable requires KEEP_LAST(1)) or chose best_effort for a sensor would be silently widened by a contract that over-declared.

§The rule, and why the asymmetry is real

The contract states what the BUILD RESERVED. W12 made that literal for the depth: the executor arena, the zenoh receive ring and both payload pools on a declared image are all sized from the declared depth. The policies are the same kind of statement: XRCE drops both reliable stream buffers to the protocol floor when every endpoint declares best_effort (RFC-0100 D3), and a transient_local endpoint is what the build counts a late-joiner slot for. So for each column, ordered by how much it ASKS FOR (deeper; reliable over best_effort; transient_local over volatile):

  • asked > declared — the registration wants more than was bought, and asking for it would ride storage the build did not reserve. TAKEN, and reported. For the two policies it is also the direction RxO tolerates: a best_effort or volatile reader still matches a reliable or transient_local writer.
  • asked < declared — taking would WIDEN what the code narrowed, and the image paid for something nothing wants. Two statements about one fact disagreeing, with no default that explains either (RFC-0100 D8). REFUSED, naming both.
  • equal, or nothing declared — returned unchanged. An image with no contract is byte-identical to every build before this wave.
  • a policy asked as SystemDefault — the code stated no opinion, so the declaration is TAKEN, silently: there is nothing to report a divergence from.

§And it says so

A take is REPORTED, once per endpoint that diverges — the C++ surface’s value is visible in the source at the call site and Rust’s is not, so a registration that quietly stopped being what lib.rs reads would be the lie this whole model exists to remove. Every take here narrows what the code asked for (a shallower queue, no retransmission, no late-joiner history), any of which can drop a sample, so each is WARN.

Called instead of check at every Rust subscription registration. The C and C++ FFI seams keep check — they have already taken the declared QoS at their own call site, so a disagreement reaching THEM is a real refusal and not a default nobody wrote.

§#[inline] is load-bearing, and it was MEASURED

An image with no contract must be byte-identical to every build before this wave, and “the table is empty so the work folds away” is a claim about the optimiser, not a fact. It was FALSE as first written: with the table threaded in as an unwrap_or(&[]) argument, honour stayed out of line — a real T nros_node::declared_qos::honour symbol, +144 bytes of text and +8 of data on the no-contract listener against origin/main, because a 40-byte QoSProfile moving in and out by value is past LLVM’s inlining budget. Matching the Option HERE, behind #[inline], is what lets a None table fold the call to Ok(qos) at every one of the fourteen registration sites and leave honour_in unreferenced. Verified by rebuilding both trees and comparing the linked binary byte for byte.