This is a Rust native client library for ROS2. It does not link to rcl, rclcpp, or any non-Rust DDS library. RustDDS is used for communication.
semio-ai/ros2-client is a fork of Atostek/ros2-client
that adds a second, rmw_zenoh-compatible ROS 2-over-Zenoh backend alongside
the original DDS one, selected at compile time by the mutually-exclusive dds
(default) / zenoh features (see
docs/decisions/0002-dual-backend-compile-time-feature-selection.md).
Upstream owns the ros2-client name on crates.io, so this fork is published as
ros2-client-multi-rmw. The library is still imported as ros2_client (the
[lib] name is pinned), so downstream code is unchanged — only the dependency
line differs:
ros2-client = { package = "ros2-client-multi-rmw", version = "0.13", default-features = false, features = ["zenoh"] }Code stays use ros2_client::…. A release is cut by merging a version bump to
master: release.yml publishes through
crates.io trusted publishing when that version is not on crates.io yet — no tag,
no token. CHANGELOG.md records each version. With the
upstream remote configured, gh pr create targets Atostek/ros2-client
unless told otherwise: pass --repo semio-ai/ros2-client.
This fork is meant to keep tracking Atostek/ros2-client, and ideally to be
retired once the Zenoh backend is upstreamed. To pull upstream changes in:
git remote add upstream https://github.com/Atostek/ros2-client.git # once
git fetch upstream
git switch master && git merge upstream/master # or: git rebase upstream/masterThe fork's changes are additive and feature-gated, so merges stay small — the divergence is concentrated in a few seams:
- the new
src/zenoh_backend/module (upstream has nothing there); - the
dds/zenohfeature gates and thecompile_error!enforcing exactly one backend (src/lib.rs); - thin backend-dispatch shims where
Context/Node/ topics are constructed.
Conflicts almost always land in those seams. Resolve by keeping the backend split, then re-check both backends and the nightly formatter before re-publishing:
cargo test # DDS (default)
cargo test --no-default-features --features zenoh,jazzy --lib
cargo +nightly-2026-07-22 fmt -- --check # the nightly CI formats withThe API is not identical to rclcpp or rclpy, because some parts would be very awkward in Rust. For example, there are no callbacks. Rust async mechanism is used instead. Alternatively, some of the functionality can be polled using the Metal I/O library.
There is a .spin() call, but it is required only to have ros2-client execute some background tasks. You can spawn an async task to run it, and retain the flow of control in your code.
Please see the included examples on how to use the various features.
- Topics, Publish and Subscribe ✅
- QoS ✅
- Serialization ✅ - via Serde
- Services: Clients and Servers ✅ (async recommended)
- Actions ✅ (async required)
- Discovery / ROS Graph update events ✅ (async)
rosoutlogging ✅- Parameters ✅
- Parameter Services (remote Parameter manipulation) ✅
- Time support
- ROS Time ✅
- Simulated time support ✅
- Steady time ✅
- Message generation: from
.msgto.rs- experimental - ROS 2 Security - experimental
ros2-client can talk to ROS 2 over either of two middleware backends,
selected at compile time by mutually exclusive Cargo features:
dds(default) — communicates via RustDDS, interoperating with ROS 2's default DDS RMWs (rmw_fastrtps,rmw_cyclonedds, …).zenoh— communicates via Zenoh, mirroring the wire protocol of the officialrmw_zenohmiddleware, so it interoperates with ROS 2 nodes runningrmw_zenoh.
Exactly one backend must be enabled; the build emits a compile_error! if both
or neither are active. The default build uses dds. Build the Zenoh backend
with:
cargo build --no-default-features --features zenohRun the bundled Zenoh example (a self-contained talker + listener over loopback):
cargo run --no-default-features --features zenoh --example zenoh_demoLike rmw_zenoh, the Zenoh backend discovers peers and exchanges the ROS graph
through Zenoh's infrastructure. For anything beyond a single process you
normally run a Zenoh router (zenohd), exactly as rmw_zenoh does, or
configure explicit peer connect/listen endpoints. The in-process examples
and tests connect two peers directly over loopback, so they need no router. See
docs/decisions/0009-zenoh-router-and-config.md
for configuration details.
| Capability | Zenoh backend | Notes |
|---|---|---|
| Topics (publish/subscribe) | ✅ | CDR payload + (seq, timestamp, gid) attachment, per rmw_zenoh |
| Services (client/server) | ✅ | Zenoh queryable / get |
| Actions | ✅ | Composed of services + a feedback topic, as in ROS 2 |
Parameters + parameter_events |
✅ | Six rcl_interfaces services + events topic |
rosout logging |
✅ | /rosout publisher + optional reader |
| Discovery / ROS graph | ✅ | Zenoh liveliness tokens + a graph cache |
| QoS | Backend-neutral profile carried in liveliness keys; not all policies enforced | |
| ROS 2 Security | ❌ | DDS-only (RustDDS security) |
Message generation (msggen) |
✅ | Backend-neutral |
The design, the rmw_zenoh mapping, and the wire-format details are documented
under docs/zenoh_study/; the design decisions are recorded
under docs/decisions/. To validate interoperability against
a real ROS 2 + rmw_zenoh stack, follow
docs/zenoh_study/interop_runbook.md.
Type hashes (send direction): REP-2016
RIHS01_…type hashes are emitted from a table of known interop types; types outside that table use a wildcard on receive and a placeholder on send. Computing hashes from parsed IDL is a tracked post-MVP follow-up (ADR-0007).
This is what is expected to work. There are no routine tests against older releases.
Select the target distribution with a Cargo feature.
Note: The distribution features
form a chain (galactic < humble < iron < jazzy < kilted < lyrical), and enabling
any of these also enables all the older features,
but the build aims to be compatible only with the latest enabled.
The default feature is currently jazzy, because it has LTS status.
Build against a specific
distribution with, e.g., cargo build --no-default-features --features humble
(--no-default-features avoids also pulling in the default).
| ROS 2 Release | ros2-client should interoperate? |
|---|---|
| A - E | Maybe. Not tested. |
| Foxy, Galactic, Humble | Yes. Build with feature galactic or humble (older Gid format). |
| Iron | Yes. Not well tested. Build with feature iron. |
| Jazzy | Yes (default). Build with feature jazzy. |
| Kilted | Yes. Build with feature kilted. |
| Lyrical | Yes. Build with feature lyrical. |
Please see test results for details.
- Experimental Zenoh middleware backend (Cargo feature
zenoh), mirroringrmw_zenoh. See "Middleware backends: DDS and Zenoh" above. - Add interoperability tests and results.
- ROS 2 distribution selection via a feature (
galactic..lyrical; defaultjazzy). Contextnow checks theROS_DISTROenvironment variable against the compiled distribution.- Upgrade to RustDDS 0.13 to improve interoperability.
- Upgrade to RustDDS 0.12, which had an API change.
- API change:
ParameterFuncmust now implementSync, so thatNodeis alsoSync. This helps in using multithreaded async executors.
AsyncActionServermethods changes from taking&mut selfinto&selffor better serving concurrent goals.- Bump RustDDS and other depencency versions
- Add example
concurrent_action_server msggenlogging can be redirected.- Additional unit tests, including the use of
tokioasync executor.
NodeNamenamespace is no longer allowed to be the empty string, because it confuses ROS 2 tools. Minimum namespace is "/".- Parameter support, incl. Paramater services
- Time support
- Subscribers can
take()samples with deserialization "seed" value. This allows more run-time control of deserialization. Upgrade to RustDDS 0.10.0.
- Adapt to separation of CDR encoding from RustDDS.
- Implement std
Errortrait forNameErrorandNodeCreateError - Async
wait_for_writerandwait_for_readerresults now implementSend.
- New feature
pre-iron-gid. The Gid.msgdefinition has changed between ROS2 Humble and Iron.ros2-clientnow uses the newer version by default. Use this feature to revert to the old definition.
- Reworked ROS 2 Discovery implementation. Now
Nodehas.status_receiver() - Async
.spin()call to run the Discovery mechanism. Clienthas.wait_for_service()- New API for naming Nodes, Topics, Services, Actions, and data types for Topics, Actions, and Services. The new API is more structured to avoid possible confusion and errors from parsing strings.
- Actions are supported
- async programming interface. This should make a built-in event loop unnecessary, as Rust async executors sort of do that already. This means that
ros2-clientis not going to implement a call similar torclcpp::spin(..).
These are re-implementations of similarly named ROS examples. They should be interoperable with ROS 2 example programs in C++ or Python.
To test this, start a server and then, in a separate terminal, a client, e.g.
ros2 run examples_rclcpp_minimal_action_server action_server_member_functions
and
cargo run --example=minimal_action_client
or
cargo run --example=minimal_action_server
and
ros2 run examples_rclpy_minimal_action_client client
You should see the client requesting for a sequence of Fibonacci numbers, and the server providing them until the requested sequence length is reached.
The included example program should be able to communicate with out-of-the-box ROS2 turtlesim example.
Install ROS2 and start the simulator by ros2 run turtlesim turtlesim_node. Then run the turtle_teleop example to control the simulator.
Teleop example program currently has the following keyboard commands:
- Cursor keys: Move turtle
qorCtrl-C: quitr: reset simulatorp: change pen color (for turtle1 only)a/b: spawn turtle1 / turtle2A/B: kill turtle1 / turtle21/2: switch control between turtle1 / turtle2d/f/g: Trigger or cancel absolute rotation action.
Install ROS2. This has been tested to work against "Galactic" release, using either eProsima FastDDS or RTI Connext DDS (rmw_connextdds, not rmw_connext_cpp).
Start server: cargo run --example=ros2_service_server
In another terminal or computer, run a client: ros2 run examples_rclpy_minimal_client client
Similar to above.
Start server: ros2 run examples_rclpy_minimal_service service
Run client: cargo run --example=ros2_service_client
arora-sdk builds on this client to give
an Arora device a ROS 2 surface over
either backend: arora-bridge-ros2 (ROS as the device's remote — keys as topics,
methods as services, task runs as actions) and arora-hal-ros2 (ROS as the
device's own hardware). The ROS 2 message definitions they carry — including
the hri_msgs / ROS4HRI human-robot-interaction
vocabulary — live in arora-msgs-ros2; this crate is the message-type-agnostic
transport underneath.
- ros2_rust is closest(?) to an official ROS2 client library. It links to ROS2
rcllibrary written in C. - rclrust is another ROS2 client library for Rust. It supports also ROS2 Services in addition to Topics. It links to ROS2 libraries, e.g.
rclandrmw. - rus2 exists, but appears to be inactive since September 2020.
Copyright 2022 Atostek Oy
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
This crate is developed and open-source licensed by Atostek Oy.
