pub trait ClientTrait {
type Error;
// Required methods
fn send_request_raw(&mut self, request: &[u8]) -> Result<i64, Self::Error>;
fn take_response_raw(
&mut self,
reply_buf: &mut [u8],
) -> Result<Option<(usize, i64)>, Self::Error>;
// Provided methods
fn refused_response_len(&self) -> Option<usize> { ... }
fn send_request<S: RosService>(
&mut self,
request: &S::Request,
req_buf: &mut [u8],
) -> Result<(), Self::Error>
where Self::Error: From<TransportError> { ... }
fn take_response<S: RosService>(
&mut self,
reply_buf: &mut [u8],
) -> Result<Option<S::Reply>, Self::Error>
where Self::Error: From<TransportError> { ... }
fn register_waker(&self, _waker: &Waker) { ... }
fn service_is_ready(&self) -> Result<bool, Self::Error>
where Self::Error: From<TransportError> { ... }
fn request_publisher_actual_qos(&self) -> QoSProfile { ... }
fn response_subscription_actual_qos(&self) -> QoSProfile { ... }
}Expand description
Service client trait for sending requests.
§Threading
&mut self on every method — the client is single-owner. For
fan-out request patterns, create one client per worker thread.
§Calling pattern
All in-tree backends route blocking waits through the executor:
send_request_raw(buf)— non-blocking; returns once the request is queued for transmission.- The executor’s
drive_ioruns. take_response_raw(buf)— non-blocking; returnsOk(Some(len))when the reply is back.
Phase-301 (issue 0240): the deprecated blocking call_raw path is
DELETED — send_request_raw + take_response_raw is the one
request/reply path, and both are required for service-capable
backends.
Required Associated Types§
Required Methods§
Sourcefn send_request_raw(&mut self, request: &[u8]) -> Result<i64, Self::Error>
fn send_request_raw(&mut self, request: &[u8]) -> Result<i64, Self::Error>
Send a service request without waiting for a reply (non-blocking).
Returns the SEQUENCE ID the backend assigned. The caller must
subsequently poll take_response_raw and
match that id against the one the reply carries.
Issue 0778 — this returned () until 2026-08-25, and every backend
computed an id and discarded it. With nothing to correlate by, a client
with two calls outstanding could not tell the replies apart, so each
backend picked a policy: cyclonedds abandoned the older request, zenoh
took the first reply. Both are wrong for send_goal and
SetParameters, which travel this path and are not idempotent.
Sourcefn take_response_raw(
&mut self,
reply_buf: &mut [u8],
) -> Result<Option<(usize, i64)>, Self::Error>
fn take_response_raw( &mut self, reply_buf: &mut [u8], ) -> Result<Option<(usize, i64)>, Self::Error>
Poll for a reply (non-blocking).
Returns Ok(Some((len, sequence_id))) when a reply has arrived,
Ok(None) if not yet available, or Err on failure. The
sequence_id is the one send_request_raw
returned for the request this answers.
It used to say “a reply to the MOST RECENTLY sent request”, which was the single-outstanding-call assumption written into the contract.
Provided Methods§
Sourcefn refused_response_len(&self) -> Option<usize>
fn refused_response_len(&self) -> Option<usize>
Issue 1632 — how many bytes the reply the most recent
take_response_raw REFUSED as too big for
the caller’s buffer needed. Subscription::refused_sample_len’s
contract, for the client: None is “the backend does not know”.
Sourcefn send_request<S: RosService>(
&mut self,
request: &S::Request,
req_buf: &mut [u8],
) -> Result<(), Self::Error>
fn send_request<S: RosService>( &mut self, request: &S::Request, req_buf: &mut [u8], ) -> Result<(), Self::Error>
Send a typed service request without waiting for a reply (non-blocking).
Serializes the request into req_buf and calls send_request_raw.
Sourcefn take_response<S: RosService>(
&mut self,
reply_buf: &mut [u8],
) -> Result<Option<S::Reply>, Self::Error>
fn take_response<S: RosService>( &mut self, reply_buf: &mut [u8], ) -> Result<Option<S::Reply>, Self::Error>
Poll for a typed reply to the most recently sent request (non-blocking).
Calls take_response_raw and deserializes if available.
Sourcefn register_waker(&self, _waker: &Waker)
fn register_waker(&self, _waker: &Waker)
Register an async waker to be notified when a reply arrives.
Called from Future::poll() implementations to store the waker.
The transport backend calls waker.wake() from its reply callback
when a response is available, enabling event-driven async without
busy-polling.
Default: no-op (backends that don’t support waking simply ignore this).
Sourcefn service_is_ready(&self) -> Result<bool, Self::Error>
fn service_is_ready(&self) -> Result<bool, Self::Error>
Whether a matching service server is currently discoverable.
Mirrors rclcpp::ClientBase::service_is_ready — the NAME is upstream’s.
The SHAPE is rcl’s: rcl_service_server_is_available(node, client, bool *is_available) returns RCL_RET_OK “if the check was made
successfully (regardless of the service readiness)”, i.e. the return
code says whether the CHECK worked and the out-param carries the ANSWER.
rclcpp collapses that to a bare bool and moves the error to
exceptions; RFC-0018 forbids exceptions, so Result<bool, _> is how the
same contract is expressed here (phase-379 W6, RFC-0036).
Returns Ok(true) if at least one matching server is CURRENTLY
discoverable, Ok(false) if none is, or Err(_) if the backend
cannot answer (e.g. XRCE — micro-XRCE-DDS-Client has no participant
enumeration).
CURRENT, not latched: upstream rmw.h says the outcome reflects a
change in either direction, and rclcpp::ClientBase::service_is_ready
calls rcl_service_server_is_available on every invocation. A backend
answers from the discovery state it maintains — the DDS built-in
topics, or the zenoh matched-server set fed by liveliness PUT/DELETE
(phase-428 W13) — so a server that goes away reads unavailable again.
Every wait loop in the tree (wait_for_service,
wait_for_action_server, their C and C++ bindings) is a spin on this
one method; there is no separate asynchronous probe (issue 1087).
Those are three answers, not two, and keeping them apart is the whole
point: is_server_ready used to sit beside this method collapsing
“don’t know” and “no server” into one false, its trait default was
true, and only zenoh overrode it — so on every other image
wait_for_service returned success without probing anything. Issue
1008 deleted it rather than fixing it, because a caller cannot recover
from an answer that does not distinguish the two.
User-facing surface: Client<S>::server_available() in Rust,
rclc_client_server_available() in C/C++. Clients use this
to gate the first request so a startup-ordering race
(client opens before server’s discovery announcement lands)
doesn’t surface as a request-side timeout.
Default impl: Err(TransportError::Unsupported) — backends
that support graph introspection (zenoh queryable interest,
DDS built-in topic readers) opt in by overriding.
Sourcefn request_publisher_actual_qos(&self) -> QoSProfile
fn request_publisher_actual_qos(&self) -> QoSProfile
The profile this entity is ACTUALLY running — what the backend GRANTED, not what the call site requested.
Upstream’s rmw_client_request_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.
Sourcefn response_subscription_actual_qos(&self) -> QoSProfile
fn response_subscription_actual_qos(&self) -> QoSProfile
The profile this entity is ACTUALLY running — what the backend GRANTED, not what the call site requested.
Upstream’s rmw_client_response_subscription_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".