Skip to main content

Publisher

Trait Publisher 

Source
pub trait Publisher {
    type Error;

Show 13 methods // Required methods fn publish_raw(&self, data: &[u8]) -> Result<(), Self::Error>; fn buffer_error(&self) -> Self::Error; fn serialization_error(&self) -> Self::Error; // Provided methods fn publish_raw_with_attachment( &self, data: &[u8], _attachment: &[u8], ) -> Result<(), Self::Error> { ... } unsafe fn publish_streamed( &self, size_cb: unsafe extern "C" fn(out_total_len: *mut usize, user_ctx: *mut c_void), chunk_cb: unsafe extern "C" fn(out_buf: *mut u8, cap: usize, out_written: *mut usize, user_ctx: *mut c_void), user_ctx: *mut c_void, ) -> Result<(), Self::Error> where Self::Error: From<TransportError> { ... } fn publish<M: RosMessage>( &self, msg: &M, buf: &mut [u8], ) -> Result<(), Self::Error> { ... } fn supports_event(&self, _kind: EventKind) -> bool { ... } unsafe fn register_event_callback( &mut self, _kind: EventKind, _deadline_ms: u32, _cb: EventCallback, _user_ctx: *mut c_void, ) -> Result<(), Self::Error> { ... } fn unsupported_event_error(&self) -> Self::Error { ... } fn assert_liveliness(&self) -> Result<(), Self::Error> { ... } fn get_gid(&self) -> Result<[u8; 24], Self::Error> where Self::Error: From<TransportError> { ... } fn count_matched_subscriptions(&self) -> Result<usize, Self::Error> where Self::Error: From<TransportError> { ... } fn actual_qos(&self) -> QoSProfile { ... }
}
Expand description

Publisher trait for sending messages.

The name is rclrs’s, the role is the backend seam: rclrs’s Publisher<T> is a typed, shared handle (Arc<PublisherState<T>>); this trait is what an RMW backend implements, raw bytes in. The typed handle a node hands an application is one type with no Arc (nros::EmbeddedPublisher<M>). Loaning exists: EmbeddedRawPublisher::try_loan / loan, zero-copy through SlotLending when the lending feature is on and the backend supports it.

§Threading

&self on publish_raw — implementors must allow concurrent publishes from multiple threads. Internal locking (or lock-free queues) is the backend’s responsibility.

§Buffer ownership

data in publish_raw is borrowed for the duration of the call. The backend must either send it inline or copy into its own buffer before returning — the slice is invalid after the call.

§Blocking

publish_raw is expected to be non-blocking on best-effort QoS and bounded-blocking on reliable QoS (waiting for outbound queue space). Backends should not block waiting for ack from a matched subscriber.

Required Associated Types§

Source

type Error

Error type for publish operations

Required Methods§

Source

fn publish_raw(&self, data: &[u8]) -> Result<(), Self::Error>

Publish a CDR-serialised message.

Returns once the message has been handed to the transport (queued or fired-and-forgotten depending on QoS). Does not wait for delivery.

Source

fn buffer_error(&self) -> Self::Error

Return a buffer-too-small error (implementation specific)

Source

fn serialization_error(&self) -> Self::Error

Return a serialization error (implementation specific)

Provided Methods§

Source

fn publish_raw_with_attachment( &self, data: &[u8], _attachment: &[u8], ) -> Result<(), Self::Error>

Phase 128.F.4 — publish with an opaque attachment block.

attachment rides alongside the payload at the wire layer. Receivers can read it back via Subscription::take_serialized_with_attachment.

Primary use case: cross-RMW bridges stamp a bridge_origin tag (the source backend’s RMW name) so a paired return bridge can deterministically drop echoed frames.

Default body delegates to publish_raw and discards the attachment — backends that do not natively carry attachments (XRCE today, DDS without a user-data hook) see no change. Backends with native attachment support (zenoh-pico’s z_publisher_put_options::attachment, Cyclone DDS user-data) override to write the bytes onto the wire.

Source

unsafe fn publish_streamed( &self, size_cb: unsafe extern "C" fn(out_total_len: *mut usize, user_ctx: *mut c_void), chunk_cb: unsafe extern "C" fn(out_buf: *mut u8, cap: usize, out_written: *mut usize, user_ctx: *mut c_void), user_ctx: *mut c_void, ) -> Result<(), Self::Error>
where Self::Error: From<TransportError>,

Phase 124.E.1 — streamed publish.

size_cb reports the total payload length once; chunk_cb fills the slot in chunks. What it saves is the CALLER-side buffer holding a whole serialised message — which is STACK, not .bss: EmbeddedPublisher::publish serialises into a per-CALL array of DEFAULT_TX_BUF, and this trait’s own fallback below stages on the caller’s stack too. There is no per-publisher staging buffer and none of it is in .bss; issue 0782 corrected that fiction on the C slot and left this copy of it standing (phase-428 W12’s F2, one layer over).

Default body — the staging-buffer fallback (124.E.2). Asks size_cb for the total length, fills a stack-allocated [u8; STAGE_CAP] via chunk_cb, then forwards to publish_raw. STAGE_CAP is the 4096 constant declared in the body, not a knob: this said NROS_MAX_STREAM_CHUNK until 2026-09-12 and no such setting has ever existed in any form, so a reader looking for it found nothing and had no way to learn the limit. Returns Err(BufferTooSmall) if the total exceeds that cap so the caller can drop back to a regular publish_raw with a heap-sized buffer.

Concrete backends opt in by overriding to stream straight into the network buffer (zenoh: write into the zenoh-pico outbound buffer; XRCE: micro-CDR streaming APIs).

§Safety

The caller must ensure user_ctx is valid for every invocation of size_cb and chunk_cb during this call, and that both callbacks obey their out-pointer contracts.

size_cb and chunk_cb may be called from the same thread that called publish_streamed. Backends MUST NOT defer the calls past the function return; the caller’s user_ctx pointer is only guaranteed valid for the duration of the call.

Source

fn publish<M: RosMessage>( &self, msg: &M, buf: &mut [u8], ) -> Result<(), Self::Error>

Publish a typed message (serializes automatically)

Source

fn supports_event(&self, _kind: EventKind) -> bool

Phase 108 — true if the backend can generate this event for this publisher. Default returns false; backends override per supported event kind.

Only EventKind::LivelinessLost and EventKind::OfferedDeadlineMissed are publisher-side events; other kinds always return false here.

Source

unsafe fn register_event_callback( &mut self, _kind: EventKind, _deadline_ms: u32, _cb: EventCallback, _user_ctx: *mut c_void, ) -> Result<(), Self::Error>

Phase 108 — register a callback fired when the named status event occurs. deadline_ms applies to EventKind::OfferedDeadlineMissed only; ignored otherwise. Default impl returns the backend’s “unsupported”-shaped error.

§Safety

cb and user_ctx must remain valid for the entity’s lifetime. Caller (typically nros-node’s typed wrapper) is responsible for keeping the closure / context arena alive.

Source

fn unsupported_event_error(&self) -> Self::Error

Phase 108 — backend’s error variant for “this event kind is not supported.” Default impl reuses serialization_error() since most backends share an Unsupported variant; backends override if they have a distinct Unsupported mapping.

Source

fn assert_liveliness(&self) -> Result<(), Self::Error>

Phase 109 — assert this publisher’s liveliness manually. Required for publishers configured with QoSLivelinessPolicy::ManualByTopic / ManualByNode; nothing to do for the other kinds.

Ok(()) MEANS THE ASSERTION REACHED THE WIRE, or that there was nothing to assert (the phase-467 RMW gap-closure design study’s Row 7). Under a kind for which QoSLivelinessPolicy::is_manual holds, a backend that cannot renew a lease a PEER can observe must answer Unsupported — an Ok(()) from a call that sent nothing is the silently-wrong shape RFC-0089 Part I refuses, and it is what the zenoh shim answered until this study. A backend is free to keep a LOCAL record of the call (zenoh drives its own LivelinessLost watchdog off one) and still report Unsupported; the return value is about what a peer can see, not about what we noted down.

The default body is Ok(()), which is correct only for a publisher whose kind is not manual — a backend that can be created with a manual kind must override.

Source

fn get_gid(&self) -> Result<[u8; 24], Self::Error>
where Self::Error: From<TransportError>,

This publisher’s own global identifier — upstream rmw_get_gid_for_publisher, the vtable’s get_gid_for_publisher.

The phase-467 RMW gap-closure design study’s Q1. Not the same question as MessageInfo::publisher_gid, which is the identity of whoever sent a message we RECEIVED. Since that study’s Q1(a) the two are one TYPE — both PUBLISHER_GID_SIZE wide, nothing truncated, no mapping — and since issue 1495 they are one VALUE on zenoh and Cyclone: a take reports the gid this publisher’s own get_gid reports, so the two compare. (XRCE and uORB answer neither.)

How many of the 24 bytes MEAN anything is a backend property. A backend whose identity is narrower zero-extends through pad_publisher_gid rather than leaving the tail undefined, so two gids naming one entity compare equal. Gids from DIFFERENT backends are never comparable.

The default is Err(Unsupported), never a fabricated or all-zero id: an all-zero gid is what an unwritten buffer holds, so handing one back would make “this backend has no identity for this publisher” indistinguishable from an answer. That is the same NULL-slot discipline the ABI header states for get_serialization_format.

Source

fn count_matched_subscriptions(&self) -> Result<usize, Self::Error>
where Self::Error: From<TransportError>,

How many subscriptions are MATCHED to this publisher — upstream rmw_publisher_count_matched_subscriptions, the vtable’s publisher_count_matched_subscriptions. Issue 1643.

Matched means QoS-compatible with THIS publisher, which is upstream’s question and a stronger one than “how many subscriptions are on the topic” (the graph’s count_subscribers): an incompatible peer is on the topic and not matched.

The default is Err(Unsupported), never Ok(0) — 0 means “nobody is listening”, and a backend that cannot tell must not say that. A caller with a weaker fallback (the node API’s topic-wide graph count) takes it on Unsupported.

Source

fn actual_qos(&self) -> QoSProfile

The profile this entity is ACTUALLY running — what the backend GRANTED, not what the call site requested.

Upstream’s rmw_publisher_get_actual_qos. The two differ whenever a backend serves something other than what it was asked for, and the difference is exactly what answers “why is nothing arriving”: a RELIABLE reader does not match a BEST_EFFORT writer, and a clamped history depth drops samples the caller believed were kept.

Per-field, and an unreportable field is an ABSENCE. A backend that determines four policies and not the fifth returns the four and leaves the fifth at its Unknown variant. Never echo the request into a field you did not read back — that is the inverse of this method’s meaning, and it is what the six C-ABI slots did until this phase.

The default is QoSProfile::QOS_PROFILE_UNKNOWN — every field an absence — which is the honest answer for a backend with no read-back at all (XRCE has none; zenoh-pico’s QoS is per-message flags). It is NOT the request, and it is NOT QOS_PROFILE_DEFAULT.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§