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(*mut usize, *mut c_void),
chunk_cb: unsafe extern "C" fn(*mut u8, usize, *mut usize, *mut c_void),
user_ctx: *mut c_void,
) -> Result<(), Self::Error>
where Self::Error: From<TransportError> { ... }
fn publish<M>(&self, msg: &M, buf: &mut [u8]) -> Result<(), Self::Error>
where M: RosMessage { ... }
fn supports_event(&self, _kind: EventKind) -> bool { ... }
unsafe fn register_event_callback(
&mut self,
_kind: EventKind,
_deadline_ms: u32,
_cb: unsafe extern "C" fn(EventKind, *const c_void, *mut c_void),
_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§
Required Methods§
Sourcefn publish_raw(&self, data: &[u8]) -> Result<(), Self::Error>
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.
Sourcefn buffer_error(&self) -> Self::Error
fn buffer_error(&self) -> Self::Error
Return a buffer-too-small error (implementation specific)
Sourcefn serialization_error(&self) -> Self::Error
fn serialization_error(&self) -> Self::Error
Return a serialization error (implementation specific)
Provided Methods§
Sourcefn publish_raw_with_attachment(
&self,
data: &[u8],
_attachment: &[u8],
) -> Result<(), Self::Error>
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.
Sourceunsafe fn publish_streamed(
&self,
size_cb: unsafe extern "C" fn(*mut usize, *mut c_void),
chunk_cb: unsafe extern "C" fn(*mut u8, usize, *mut usize, *mut c_void),
user_ctx: *mut c_void,
) -> Result<(), Self::Error>
unsafe fn publish_streamed( &self, size_cb: unsafe extern "C" fn(*mut usize, *mut c_void), chunk_cb: unsafe extern "C" fn(*mut u8, usize, *mut usize, *mut c_void), user_ctx: *mut c_void, ) -> Result<(), Self::Error>
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.
Sourcefn publish<M>(&self, msg: &M, buf: &mut [u8]) -> Result<(), Self::Error>where
M: RosMessage,
fn publish<M>(&self, msg: &M, buf: &mut [u8]) -> Result<(), Self::Error>where
M: RosMessage,
Publish a typed message (serializes automatically)
Sourcefn supports_event(&self, _kind: EventKind) -> bool
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.
Sourceunsafe fn register_event_callback(
&mut self,
_kind: EventKind,
_deadline_ms: u32,
_cb: unsafe extern "C" fn(EventKind, *const c_void, *mut c_void),
_user_ctx: *mut c_void,
) -> Result<(), Self::Error>
unsafe fn register_event_callback( &mut self, _kind: EventKind, _deadline_ms: u32, _cb: unsafe extern "C" fn(EventKind, *const c_void, *mut c_void), _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.
Sourcefn unsupported_event_error(&self) -> Self::Error
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.
Sourcefn assert_liveliness(&self) -> Result<(), Self::Error>
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.
Sourcefn get_gid(&self) -> Result<[u8; 24], Self::Error>
fn get_gid(&self) -> Result<[u8; 24], Self::Error>
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.
Sourcefn count_matched_subscriptions(&self) -> Result<usize, Self::Error>
fn count_matched_subscriptions(&self) -> Result<usize, Self::Error>
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.
Sourcefn actual_qos(&self) -> QoSProfile
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".
Implementations on Foreign Types§
Source§impl Publisher for CffiPublisher
impl Publisher for CffiPublisher
Source§fn actual_qos(&self) -> QoSProfile
fn actual_qos(&self) -> QoSProfile
The profile the backend GRANTED, read back at create time.
Answers off the retained value rather than re-entering the backend:
upstream’s rmw_publisher_get_actual_qos is a property of the entity,
and a backend call here would make an accessor a transport operation on
a &self that any callback may hold.
Source§fn get_gid(&self) -> Result<[u8; 24], TransportError>
fn get_gid(&self) -> Result<[u8; 24], TransportError>
Upstream rmw_get_gid_for_publisher, through the vtable slot.
The phase-467 RMW gap-closure design study’s Q1, step 2. A NULL slot
answers Unsupported — uORB and XRCE NULL it. The Rust adapter table
zenoh rides FILLS it now (it was NULL there too until this study, so a
Rust backend with a real identity could not report one). Never an
all-zero gid: that is what the uninitialised rmw_gid_t below holds,
so returning it on the NULL path would make “no identity” read exactly
like an answer.
The backend’s implementation_identifier is deliberately DROPPED
here. nros_rmw_cffi_register_named admits several backends in one
image and gids from two of them are not comparable, so upstream’s
pairing matters — but the trait answers bytes, and the identifier a
caller needs is the one on the SESSION it asked. Carrying a second
copy per publisher would be a second place for it to disagree.
Source§fn count_matched_subscriptions(&self) -> Result<usize, TransportError>
fn count_matched_subscriptions(&self) -> Result<usize, TransportError>
Upstream rmw_publisher_count_matched_subscriptions, through the
vtable slot. Issue 1643 — the slot’s first RUNTIME reader (the node
API’s get_subscription_count); until then only an interop probe and
the backend’s own tests asked. A NULL slot answers Unsupported.