HORUS vs ROS2

This page compares HORUS and ROS2 for robotics development. Both are frameworks for building robot software — but they make fundamentally different architectural choices. This guide helps you decide which fits your project.

Already using ROS2? See Coming from ROS2 for a migration guide with side-by-side code examples.


Quick Summary

AspectHORUSROS2
IPC Latency87 ns (shared memory)50-100 μs (DDS)
Speedup575x faster for small messagesBaseline
ArchitectureTick-based, single-processCallback-based, multi-process
Execution OrderDeterministic (priority + order)Non-deterministic (callback queue)
Real-TimeAuto-detected from .rate() / .budget()Manual DDS QoS configuration
Config Files1 file (horus.toml)3+ files (package.xml, CMakeLists.txt, launch.py)
LanguagesRust, PythonC++, Python
EcosystemGrowing (core framework + package registry)Massive (thousands of packages, 15+ years)
Multi-MachineNot yet (single-machine focus)Native (DDS network transport)
Visualizationhorus monitor (web + TUI)RViz2, Foxglove, rqt
LicenseApache 2.0Apache 2.0

Performance

Message Passing Latency

HORUS uses shared memory directly. ROS2 uses DDS middleware (FastDDS, CycloneDDS) which adds serialization, discovery, and network stack overhead — even for same-machine communication.

Message TypeSizeHORUSROS2 (FastDDS)Speedup
CmdVel16 B~85 ns~50 μs588x
IMU304 B~400 ns~55 μs138x
Odometry736 B~600 ns~60 μs100x
LaserScan1.5 KB~900 ns~70 μs78x
PointCloud (1K pts)12 KB~12 μs~150 μs13x
PointCloud (10K pts)120 KB~360 μs~800 μs2.2x

The speedup is most dramatic for small, frequent messages (CmdVel, IMU) — exactly the messages that matter for tight control loops.

Throughput

MetricHORUSROS2
Small messages (16B)2.7M msg/s~20K msg/s
IMU messages (304B)1.8M msg/s~18K msg/s
Mixed workload1.5M msg/s~15K msg/s

Real-Time Metrics

MetricHORUSROS2
Timing jitter±10 μs±100-500 μs
WCET overhead<5 μs per node~50-200 μs per callback
Deadline enforcementBuilt-in (.budget(), .deadline())Manual (rmw QoS)
Emergency stop<100 μsApplication-dependent

Architecture

HORUS: Tick-Based, Deterministic

Scheduler runs one tick cycle:
  → Node 0 (order=0, safety monitor)
  → Node 1 (order=1, sensor reader)
  → Node 2 (order=2, controller)
  → Node 3 (order=3, actuator)
  → repeat

Every tick executes nodes in a guaranteed order. Node 2 always sees Node 1's latest data. No race conditions, no callback scheduling surprises.

ROS2: Callback-Based, Event-Driven

Executor spins:
  → timer callback fires (sensor)
  → subscription callback fires (controller)
  → timer callback fires (actuator)
  → order depends on event timing

Callbacks fire when events arrive. Execution order depends on timing, message arrival, and executor implementation. Two runs of the same code may execute callbacks in different orders.

Why This Matters

For a motor controller that reads IMU data and sends actuator commands:

  • HORUS: IMU node ticks first (order=0), controller ticks second (order=1), actuator ticks third (order=2). Every cycle, guaranteed.
  • ROS2: IMU callback might fire before or after the controller callback. Under load, callbacks can be delayed or reordered.

For safety-critical systems (surgical robots, industrial cobots), deterministic execution order is not optional.


Developer Experience

Project Setup

HORUS (1 file):

# horus.toml — single source of truth
[package]
name = "my-robot"
version = "0.1.0"

[dependencies]
serde = "1.0"
nalgebra = "0.32"

ROS2 (3+ files):

<!-- package.xml -->
<package format="3">
  <name>my_robot</name>
  <buildtool_depend>ament_cmake</buildtool_depend>
  <depend>rclcpp</depend>
  <depend>sensor_msgs</depend>
</package>
# CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
# launch/robot.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node

Node Definition

HORUS (Rust):

struct MotorController {
    commands: Topic<f32>,
}

impl Node for MotorController {
    fn tick(&mut self) {
        if let Some(cmd) = self.commands.recv() {
            set_motor_velocity(cmd);
        }
    }
}

ROS2 (C++):

class MotorController : public rclcpp::Node {
  rclcpp::Subscription<std_msgs::msg::Float32>::SharedPtr sub_;
  void callback(std_msgs::msg::Float32::SharedPtr msg) {
    set_motor_velocity(msg->data);
  }
public:
  MotorController() : Node("motor_controller") {
    sub_ = create_subscription<std_msgs::msg::Float32>(
      "commands", 10,
      std::bind(&MotorController::callback, this, std::placeholders::_1)
    );
  }
};

HORUS: 10 lines. ROS2: 15 lines + shared pointers + bind + QoS depth parameter.

CLI

TaskHORUSROS2
Create projecthorus new my_robotros2 pkg create my_robot + edit CMakeLists.txt
Buildhorus buildcolcon build
Runhorus runros2 run my_robot my_node
List topicshorus topic listros2 topic list
Echo topichorus topic echo velocityros2 topic echo /velocity
Monitorhorus monitorrqt (separate install)
Add dependencyhorus add serdeEdit package.xml + CMakeLists.txt + rosdep install

When to Use HORUS

Choose HORUS when:

  • You need sub-microsecond IPC latency (control loops >100Hz)
  • Deterministic execution order matters (safety-critical systems)
  • You want a single-file project config
  • Your robot runs on a single machine
  • You prefer Rust's safety guarantees
  • You want integrated monitoring and CLI tooling
  • You're starting a new project (no ROS2 migration debt)

Choose ROS2 when:

  • You need multi-machine communication (distributed robots, fleet management)
  • You need RViz2 for 3D visualization
  • You depend on specific ROS2 packages (MoveIt2, Nav2, SLAM Toolbox)
  • Your team already knows ROS2
  • You need the larger ecosystem (drivers, integrations, community support)
  • You need enterprise support options

Use both:

  • HORUS for real-time control on the robot (sensors, actuators, safety)
  • ROS2 for high-level planning, visualization, fleet management on separate machines

Feature Comparison

FeatureHORUSROS2
Pub/Sub TopicsYes (shared memory)Yes (DDS)
Services (RPC)BetaYes
Actions (long-running)BetaYes
ParametersYes (RuntimeParams)Yes (Parameter Server)
Transform FramesYes (TransformFrame)Yes (tf2)
Recording/ReplayYes (Record/Replay)Yes (rosbag2)
MonitoringYes (web + TUI)rqt, Foxglove (separate)
Launch SystemYAML launch filesPython/XML/YAML launch
Package ManagerYes (horus registry)Yes (rosdep, bloom)
Deterministic ModeYes (SimClock)Partial (use_sim_time)
Safety MonitorBuilt-in (watchdog, degradation)Application-level
Deadline EnforcementBuilt-in (.budget(), .deadline())Manual (rmw QoS)
Multi-MachineNot yetYes (DDS discovery)
3D VisualizationNot yetRViz2
Simulationhorus-sim3d (Bevy + Rapier)Gazebo, Isaac Sim
Message IDLRust structs (#[derive(Copy)]).msg/.srv/.action files

Migration Path

HORUS and ROS2 can coexist. Common migration strategies:

  1. Start new subsystems in HORUS — Keep existing ROS2 for high-level, add HORUS for real-time control
  2. Bridge approach — Run a ROS2 bridge node that translates between DDS topics and HORUS topics
  3. Full migration — Replace ROS2 nodes one-by-one with HORUS equivalents

See Coming from ROS2 for detailed migration guidance with side-by-side code examples.


Summary

HORUS is faster, simpler, and more deterministic for single-machine robotics. ROS2 has a larger ecosystem and multi-machine networking. The right choice depends on your specific requirements — many production robots benefit from using both.