Message Binding Generation
nros uses generated Rust bindings for ROS 2 message types. The nros generate-rust command generates no_std compatible bindings from package.xml dependencies.
Overview
The binding generator lives in the in-tree CLI sub-workspace at packages/cli/ and provides:
nrosstandalone binary- Pure Rust,
no_stdcompatible output usingheaplesstypes - Automatic dependency resolution via ament index or bundled interfaces
.cargo/config.tomlgeneration for crate patches
Prerequisites
-
package.xml in project root - Declares ROS interface dependencies
<?xml version="1.0"?> <package format="3"> <name>my_package</name> <version>0.1.0</version> <description>My nros package</description> <maintainer email="dev@example.com">Developer</maintainer> <license>Apache-2.0</license> <depend>std_msgs</depend> <depend>geometry_msgs</depend> <export> <build_type>ament_cargo</build_type> </export> </package> -
nros tool built + on PATH
The
nrosCLI is built from the in-tree sub-workspace atpackages/cli/(Phase 218) and put on PATH by the activate file:# From the nano-ros repository root source ./activate.sh # OR: direnv allow / source ./activate.fish just setup-cli # builds packages/cli/target/release/nrosSee Installation for the full walkthrough.
-
ROS 2 environment (optional for standard types)
Standard interfaces (
std_msgs,builtin_interfaces) are bundled with nros and work without ROS 2. For additional packages (e.g.,geometry_msgs,sensor_msgs), source a ROS 2 environment:source /opt/ros/humble/setup.bash
Workflow
Step 1: Create package.xml
Declare your ROS interface dependencies in <depend> tags:
<depend>std_msgs</depend> <!-- For std_msgs::msg::Int32, String, etc. -->
<depend>example_interfaces</depend> <!-- For service types -->
Step 2: Generate bindings
cd my_project
nros generate-rust
This will:
- Parse
package.xmlto find dependencies - Resolve transitive dependencies (ament index + bundled interfaces)
- Filter to interface packages (those with msg/srv/action)
- Generate bindings to
generated/directory
Step 3: Add dependencies to Cargo.toml
Reference the generated crates using crates.io version specifiers:
[dependencies]
std_msgs = { version = "*", default-features = false }
example_interfaces = { version = "*", default-features = false }
The .cargo/config.toml patches redirect these to local paths.
Why msg crates are RMW-agnostic
Generated msg crates carry only the wire-format data type — no backend-specific code, no per-RMW Cargo feature. A user manifest is the plain pair:
[dependencies]
std_msgs = { version = "*", default-features = false }
nros = { version = "*", features = ["rmw-cyclonedds"] } # RMW choice lives here
Transport choice and message schema are orthogonal concerns and the manifest reflects that. This matches upstream rclcpp + rclrs, which both ship msg packages RMW-agnostic and let the RMW pick which descriptor representation it wants at runtime.
For DDS-based backends that need a per-type descriptor on the wire
(Cyclone DDS today), the nros-rmw-cyclonedds shim builds those
descriptors lazily on first pub/sub for a given message type,
walks the static field schema exposed by nros-serdes (the
Message trait with const TYPE_NAME + const FIELDS), and caches
the result in a bounded no_std registry. No per-msg-pkg backend
code is required.
Tracking + sizing knob (NROS_CYCLONEDDS_MAX_TYPES): see
docs/roadmap/phase-212-ux-cargo-native-and-file-consolidation.md
section 212.K.7.
Git Dependency Workflow
For projects that consume nros as a git dependency (not from within the nros repo), use --nano-ros-git instead of --nano-ros-path:
Step 1: Add git dependency to Cargo.toml:
[dependencies]
nros = { git = "https://github.com/jerry73204/nano-ros", default-features = false, features = ["std"] }
std_msgs = { version = "*", default-features = false }
Step 2: Create package.xml (same as above).
Step 3: Generate bindings with git patches:
source /opt/ros/humble/setup.bash
nros generate-rust --config --nano-ros-git
This generates .cargo/config.toml with git-based patches:
[patch.crates-io]
nros-core = { git = "https://github.com/jerry73204/nano-ros" }
nros-serdes = { git = "https://github.com/jerry73204/nano-ros" }
std_msgs = { path = "generated/std_msgs" }
builtin_interfaces = { path = "generated/builtin_interfaces" }
Step 4: Use in code
#![allow(unused)]
fn main() {
use std_msgs::msg::Int32;
use example_interfaces::srv::{AddTwoInts, AddTwoIntsRequest, AddTwoIntsResponse};
let msg = Int32 { data: 42 };
}
Command Options
nros generate-rust [OPTIONS]
Options:
--manifest <PATH> Path to package.xml [default: package.xml]
-o, --output <DIR> Output directory [default: generated]
--config Generate .cargo/config.toml with [patch.crates-io] entries
--nano-ros-path <PATH> Path to nros crates (for config patches, local dev)
--nano-ros-git Use nros git repo for config patches (external users)
--force Overwrite existing bindings
-v, --verbose Enable verbose output
Generated Output Structure
my_project/
├── package.xml # Your dependency declarations
├── Cargo.toml # Your package manifest
├── src/
│ └── main.rs # Your code using generated types
├── generated/ # Generated bindings (do not edit)
│ ├── std_msgs/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs # #![no_std]
│ │ └── msg/
│ │ ├── mod.rs
│ │ └── int32.rs
│ └── builtin_interfaces/ # Transitive dependency
│ └── ...
└── .cargo/
└── config.toml # [patch.crates-io] entries
Generated Code Features
no_std by default:
#![allow(unused)]
#![no_std]
fn main() {
pub mod msg;
}
std feature for optional std support:
[features]
default = []
std = ["nros-core/std", "nros-serdes/std"]
heapless types for embedded:
#![allow(unused)]
fn main() {
pub struct String {
pub data: heapless::String<256>,
}
pub struct Arrays {
pub data: heapless::Vec<i32, 64>,
}
}
Service types with Request/Response:
#![allow(unused)]
fn main() {
pub struct AddTwoInts;
pub struct AddTwoIntsRequest { pub a: i64, pub b: i64 }
pub struct AddTwoIntsResponse { pub sum: i64 }
impl RosService for AddTwoInts {
type Request = AddTwoIntsRequest;
type Reply = AddTwoIntsResponse;
}
}
Standalone Package Mode
Examples are configured as standalone packages (excluded from workspace) because each has its own .cargo/config.toml patches. Build each example from its own directory:
cd examples/native/rust/talker && cargo build --features zenoh
cd examples/native/rust/service-client && cargo build --features zenoh
Regenerating Bindings
To regenerate after ROS package updates or dependency changes:
nros generate-rust --force
Bundled Interfaces
nros ships standard .msg files for common packages so codegen works without a
ROS 2 environment:
std_msgs(Bool, Int32, String, Header, etc.)builtin_interfaces(Time, Duration)
These are located at packages/codegen/interfaces/. When a ROS 2 environment is sourced,
the ament index takes precedence over bundled files.
Troubleshooting
“Package ‘X’ not found in ament index or bundled interfaces”
- For standard types (
std_msgs,builtin_interfaces): should work without ROS 2 - For other packages: source ROS 2 environment:
source /opt/ros/humble/setup.bash - Check package is installed:
ros2 pkg list | grep X - Install if missing:
sudo apt install ros-humble-X
Build errors with generated code
- Regenerate with
--forceflag - Check nros crate compatibility
C Code Generation (CMake)
The nano_ros_generate_interfaces() CMake function generates C bindings for .msg, .srv,
and .action files. It uses a bundled codegen library — no external nros binary needed.
Prerequisites
nano_ros_generate_interfaces() becomes available automatically once
the consumer’s CMakeLists.txt invokes add_subdirectory(nano-ros).
The codegen tool ships inside the in-tree nros CLI binary
(packages/cli/target/release/nros, Phase 218; Phase 195.D had
retired the nros-codegen submodule); cmake auto-resolves it from
PATH / packages/cli/target/release/ / the transitional
${NROS_HOME:-~/.nros}/bin/. No separate build step.
Usage
See examples/native/c/custom-msg/CMakeLists.txt for a complete example.