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 response_info(&self) -> ServiceInfo { ... }
fn send_request<S>(
&mut self,
request: &<S as RosService>::Request,
req_buf: &mut [u8],
) -> Result<i64, Self::Error>
where S: RosService,
Self::Error: From<TransportError> { ... }
fn take_response<S>(
&mut self,
reply_buf: &mut [u8],
) -> Result<Option<(<S as RosService>::Reply, i64)>, Self::Error>
where S: RosService,
Self::Error: From<TransportError> { ... }
fn take_response_for<S>(
&mut self,
sequence_id: i64,
reply_buf: &mut [u8],
) -> Result<Option<<S as RosService>::Reply>, Self::Error>
where S: RosService,
Self::Error: From<TransportError> { ... }
fn register_waker(&self, _waker: &Waker) -> bool { ... }
fn service_is_ready(&self) -> Result<bool, Self::Error>
where Self::Error: From<TransportError> { ... }
fn get_gid(&self) -> Result<[u8; 24], 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 response_info(&self) -> ServiceInfo
fn response_info(&self) -> ServiceInfo
Issue 2136 — the metadata of the reply the most recent successful
take_response_raw returned: upstream
rmw_take_response’s rmw_service_info_t, the client twin of
ServiceTrait::request_info.
request_id— the request this reply answers: this client’s own GID and the sequence number of its request;source_timestamp— when the SERVER sent the reply, on its clock;received_timestamp— when this client received it, on its clock.
Read it after a take and before the next one. A field the backend
cannot report is None, and the default — for a backend with no
per-reply metadata at all — is
ServiceInfo::NOT_REPORTED.
Sourcefn send_request<S>(
&mut self,
request: &<S as RosService>::Request,
req_buf: &mut [u8],
) -> Result<i64, Self::Error>
fn send_request<S>( &mut self, request: &<S as RosService>::Request, req_buf: &mut [u8], ) -> Result<i64, 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, returning the SEQUENCE ID
the backend assigned — crate::SEQUENCE_ID_UNKNOWN on a backend that
cannot assign one. Hand it to
take_response_for so this call takes only
its own reply.
Issue 2033 — this returned () after issue 2009, discarding the id the
raw path had carried since issue 0778, so a typed caller had nothing to
correlate by.
Sourcefn take_response<S>(
&mut self,
reply_buf: &mut [u8],
) -> Result<Option<(<S as RosService>::Reply, i64)>, Self::Error>
fn take_response<S>( &mut self, reply_buf: &mut [u8], ) -> Result<Option<(<S as RosService>::Reply, i64)>, Self::Error>
Poll for the next typed reply (non-blocking), WHICHEVER request it answers.
Returns Ok(Some((reply, sequence_id))): sequence_id is the id
send_request returned for the request this
reply answers, and the caller decides whom it belongs to — upstream’s
rcl_take_response request header. A caller with ONE request in mind
wants take_response_for, which drops the
replies of requests it abandoned.
Issue 2033 — this used to return the reply alone and was documented as “the reply to the most recently sent request”, which it never checked: after a resend or a timeout, the abandoned request’s late reply was handed to the new one.
Sourcefn take_response_for<S>(
&mut self,
sequence_id: i64,
reply_buf: &mut [u8],
) -> Result<Option<<S as RosService>::Reply>, Self::Error>
fn take_response_for<S>( &mut self, sequence_id: i64, reply_buf: &mut [u8], ) -> Result<Option<<S as RosService>::Reply>, Self::Error>
Poll for the typed reply to the request sequence_id names
(non-blocking), dropping any reply that answers a different one.
sequence_id is what send_request returned.
The rule is crate::correlation’s, the one every other client
surface applies (issue 2009): a reply is delivered only to the request
whose id it carries, a stale reply is DROPPED (at most
crate::MAX_STALE_DRAIN_PER_POLL per call), and an id-less reply or
request (crate::SEQUENCE_ID_UNKNOWN) matches the sole request in
mind. Ok(None) — nothing for this request yet.
Sourcefn register_waker(&self, _waker: &Waker) -> bool
fn register_waker(&self, _waker: &Waker) -> bool
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.
Returns false when the backend cannot wake waker (issue 1940);
the caller then polls. Default: false.
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 get_gid(&self) -> Result<[u8; 24], Self::Error>
fn get_gid(&self) -> Result<[u8; 24], Self::Error>
This client’s own global identifier — upstream (Jazzy)
rmw_get_gid_for_client, the vtable’s get_gid_for_client. Issue
1978.
The client twin of Publisher::get_gid: the identity a server keys
a request id on (zenoh: the gid stamped into every request
attachment; Cyclone: the request writer’s GUID), zero-extended to
PUBLISHER_GID_SIZE by
pad_publisher_gid.
The default is Err(Unsupported), never an all-zero id — the same
discipline, for the same reason, as Publisher::get_gid.
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".
Implementations on Foreign Types§
Source§impl ClientTrait for CffiClient
impl ClientTrait for CffiClient
Source§fn get_gid(&self) -> Result<[u8; 24], TransportError>
fn get_gid(&self) -> Result<[u8; 24], TransportError>
Issue 1978 — upstream (Jazzy) rmw_get_gid_for_client, through the
get_gid_for_client slot. The client twin of
CffiPublisher::get_gid; a NULL slot answers
Unsupported, never an all-zero gid.
Source§fn request_publisher_actual_qos(&self) -> QoSProfile
fn request_publisher_actual_qos(&self) -> QoSProfile
What the REQUEST endpoint — the publisher this client sends requests on — was granted.
Source§fn response_subscription_actual_qos(&self) -> QoSProfile
fn response_subscription_actual_qos(&self) -> QoSProfile
What the RESPONSE endpoint — the subscription this client takes replies
on — was granted. See Self::request_publisher_actual_qos.