Skip to main content

ClientTrait

Trait ClientTrait 

Source
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>(
        &mut self,
        request: &<S as RosService>::Request,
        req_buf: &mut [u8],
    ) -> Result<(), 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>, Self::Error>
       where S: RosService,
             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:

  1. send_request_raw(buf) — non-blocking; returns once the request is queued for transmission.
  2. The executor’s drive_io runs.
  3. take_response_raw(buf) — non-blocking; returns Ok(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§

Source

type Error

Error type for service operations

Required Methods§

Source

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.

Source

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§

Source

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”.

Source

fn send_request<S>( &mut self, request: &<S as RosService>::Request, req_buf: &mut [u8], ) -> Result<(), Self::Error>
where S: RosService, Self::Error: From<TransportError>,

Send a typed service request without waiting for a reply (non-blocking).

Serializes the request into req_buf and calls send_request_raw.

Source

fn take_response<S>( &mut self, reply_buf: &mut [u8], ) -> Result<Option<<S as RosService>::Reply>, Self::Error>
where S: RosService, Self::Error: From<TransportError>,

Poll for a typed reply to the most recently sent request (non-blocking).

Calls take_response_raw and deserializes if available.

Source

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).

Source

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

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.

Source

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.

Source

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

Source§

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

What the RESPONSE endpoint — the subscription this client takes replies on — was granted. See Self::request_publisher_actual_qos.

Source§

type Error = TransportError

Source§

fn send_request_raw(&mut self, request: &[u8]) -> Result<i64, TransportError>

Source§

fn take_response_raw( &mut self, reply_buf: &mut [u8], ) -> Result<Option<(usize, i64)>, TransportError>

Source§

fn refused_response_len(&self) -> Option<usize>

Source§

fn service_is_ready(&self) -> Result<bool, TransportError>

Implementors§