Custom Messages & Performance
Need a message type that doesn't exist in the standard library? Use the message! macro. HORUS handles all the optimization automatically — you never need to think about serialization or memory layout.
For performance-sensitive applications: standard fixed-size types transfer at ~50ns (zero-copy), variable-size types at ~167ns (serialized). No configuration needed.
Automatic Optimization
When you create a Topic<T>, HORUS inspects the message type at compile time and selects the fastest transfer strategy:
- Fixed-size types (no heap allocations) use raw memory copy — no serialization overhead
- Variable-size types (containing
String,Vec, etc.) use fast bincode serialization
You always use the same API. The optimization is invisible:
// simplified
use horus::prelude::*;
// Fixed-size type — automatically uses zero-copy (~50ns cross-process)
let cmd_topic: Topic<CmdVel> = Topic::new("cmd_vel")?;
cmd_topic.send(CmdVel::new(1.0, 0.5));
// Variable-size type — automatically uses serialization (~167ns cross-process)
let log_topic: Topic<String> = Topic::new("log")?;
log_topic.send("Motor started".to_string());
Performance Comparison
| Type Category | Cross-Process Latency | Examples |
|---|---|---|
| Fixed-size (zero-copy) | ~50-85ns | CmdVel, Imu, Pose2D, Heartbeat |
| Variable-size (serialized) | ~167ns | String, Vec<f32>, HashMap<K,V> |
Same-process communication is fast for both categories since no shared memory serialization is needed.
For most applications, the ~167ns serialized path is more than fast enough. Only high-frequency control loops running at 1kHz+ benefit noticeably from the zero-copy path.
Custom Messages
Use the message! macro to define your own message types. Add #[fixed] for zero-copy shared memory transport (~50ns) when all fields are primitives:
// simplified
use horus::prelude::*;
message! {
#[fixed]
/// Motor feedback — zero-copy via shared memory (~50ns)
MotorFeedback {
timestamp_ns: u64,
motor_id: u32,
velocity: f32,
current_amps: f32,
temperature_c: f32,
}
}
let feedback: Topic<MotorFeedback> = Topic::new("motor.feedback")?;
feedback.send(MotorFeedback {
timestamp_ns: 0,
motor_id: 1,
velocity: 3.14,
current_amps: 0.5,
temperature_c: 45.0,
});
For messages with String, Vec, or other dynamic data, omit #[fixed] — HORUS uses serialization transport automatically (~167ns):
// simplified
message! {
/// Diagnostic log — flexible, serialized
DiagLog {
node_name: String,
message: String,
level: u8,
}
}
Built-in Message Types
All standard HORUS message types are pre-optimized. Fixed-size types automatically use the zero-copy fast path.
Geometry
| Message | Description |
|---|---|
CmdVel | 2D velocity command (linear + angular) |
Pose2D | 2D position and orientation |
Twist | 3D linear and angular velocity |
TransformStamped | 3D transformation with timestamp |
Point3 | 3D point |
Vector3 | 3D vector |
Quaternion | Rotation quaternion |
Sensors
| Message | Description |
|---|---|
Imu | Inertial measurement unit data |
LaserScan | 2D laser range data |
Odometry | Position/velocity estimate |
RangeSensor | Single distance measurement |
BatteryState | Battery level and status |
NavSatFix | GPS position |
Control
| Message | Description |
|---|---|
MotorCommand | Individual motor control |
DifferentialDriveCommand | Differential drive control |
ServoCommand | Servo position/velocity |
JointCommand | Joint-level control |
TrajectoryPoint | Trajectory waypoint |
PidConfig | PID controller parameters |
Diagnostics
| Message | Description |
|---|---|
Heartbeat | Liveness signal |
NodeHeartbeat | Per-node health status |
DiagnosticStatus | General status report |
EmergencyStop | Emergency stop signal |
SafetyStatus | Safety system state |
ResourceUsage | CPU/memory usage |
DiagnosticValue | Single diagnostic measurement |
DiagnosticReport | Full diagnostic report |
Navigation
| Message | Description |
|---|---|
NavGoal | Navigation goal |
GoalResult | Goal completion result |
Waypoint | Navigation waypoint |
NavPath | Sequence of waypoints |
PathPlan | Planned path |
VelocityObstacle | Velocity obstacle for avoidance |
VelocityObstacles | Set of velocity obstacles |
Force/Haptics
| Message | Description |
|---|---|
WrenchStamped | Force/torque measurement |
ForceCommand | Force control command |
ImpedanceParameters | Impedance control config |
ContactInfo | Contact detection data |
HapticFeedback | Haptic output command |
Input
| Message | Description |
|---|---|
JoystickInput | Gamepad/joystick state |
KeyboardInput | Keyboard key events |
Tensor
| Message | Description |
|---|---|
Tensor | Fixed-size tensor descriptor |
// simplified
use horus::prelude::*;
// All built-in messages use the fastest available path automatically
let cmd: Topic<CmdVel> = Topic::new("cmd_vel")?;
let pose: Topic<Pose2D> = Topic::new("robot_pose")?;
let imu: Topic<Imu> = Topic::new("imu_data")?;
let estop: Topic<EmergencyStop> = Topic::new("emergency_stop")?;
Design Decisions
Why automatic optimization instead of manual annotation?
In ROS2, you choose between message types and serialization strategies manually. HORUS inspects the type at compile time — if all fields are fixed-size primitives (no String, Vec, or heap pointers), it uses raw memory copy. If any field is dynamic, it uses fast bincode serialization. You always use the same Topic<T> API. The optimization is invisible and always correct.
Why the message! macro instead of raw derives?
The macro generates the correct trait implementations (Clone, Serialize, Deserialize, and optionally Copy for fixed-size types) in one step. It also generates LogSummary for TUI debug logging and validates that #[fixed] types actually have fixed-size fields at compile time — catching mistakes that would otherwise cause silent performance degradation.
Why separate fixed-size and variable-size paths?
Fixed-size types can be memcpy'd directly into shared memory — no serialization, no allocation, ~50 ns. Variable-size types must be serialized because the receiver doesn't know the exact layout (how long is the String?). The two paths are invisible to the user but provide a 2–3x latency difference that matters at 1 kHz+.
Trade-offs
| Gain | Cost |
|---|---|
| Automatic optimization — fastest path selected at compile time | Must understand fixed vs variable-size to optimize manually |
| Zero-copy for fixed-size types — ~50 ns cross-process | Fixed-size types can't contain String, Vec, or Box |
Same API for all types — Topic<T> works identically | Variable-size types are ~3x slower (~167 ns) than fixed-size |
message! macro — one-line type definition with all derives | Macro syntax differs slightly from raw struct definition |
See Also
- Topics — Full Reference — The unified communication API
- Message Types — Full message type reference with all 70+ types
- Topic API — Complete Topic method documentation
- Architecture — How communication fits into the HORUS architecture