What is HORUS?
HORUS is a framework for building applications with multiple independent components that communicate through ultra-low-latency shared memory. Each component handles one responsibility, and they connect together to form complex systems.
The Core Idea
Instead of writing one monolithic program, you build:
- Independent Nodes - Each component is self-contained
- Connected by Topics - Nodes communicate through named channels
- Run by a Scheduler - HORUS manages execution order
Example: A robot control system might have:
- SensorNode (reads camera)
- VisionNode (detects objects)
- ControlNode (moves motors)
- SafetyNode (prevents collisions)
Each node runs independently, sharing data through topics like "camera.image", "detected.objects", "motor.commands".
Key Features
1. Low-Latency Communication
HORUS achieves 20 ns same-thread and 63 ns cross-thread for an uncontended 1:1 topic, and 151 ns one-way cross-process — roughly 33x lower than ROS 2's published REP 2014 reference for default DDS (~5µs). HORUS's figures are measured here by all_paths_latency; the ROS 2 figure is quoted, not measured. The system automatically selects the optimal shared-memory backend based on your topology (how many publishers and subscribers a topic has, and whether the message type is plain data). No configuration needed.
This performance enables tight control loops and high-frequency data processing without introducing significant delay.
2. Clean APIs
HORUS provides idiomatic Rust APIs:
use horus::prelude::*;
// Create a publisher
let publisher: Topic<f32> = Topic::new("temperature")?;
// Create a subscriber
let subscriber: Topic<f32> = Topic::new("temperature")?;
// Send data
publisher.send(25.0);
// Receive data
if let Some(temp) = subscriber.recv() {
println!("Temperature: {:.1}C", temp);
}
With the node macro:
node! {
SensorNode {
pub {
temperature: f32 -> "temperature",
}
tick {
self.temperature.send(25.0);
}
}
}
3. Built-in Developer Tools
# Create a new project
horus new my_project
# Run your application
horus run
# Monitor in real-time (ships as a plugin — install once)
horus install horus-monitor
export PATH="$HOME/.config/horus/bin:$PATH" # where the plugin lands;
# horus monitor does not search it
horus monitor
horus monitor is a built-in command, but the viewer it launches ships as a separate plugin — the first run tells you to install it with horus install horus-monitor.
The monitor displays:
- Running nodes
- Message flow between components
- Performance metrics (latency, throughput)
- Debugging information
4. Multi-Language Support
Python:
import horus
def sensor_tick(node):
node.send("temperature", 25.0)
sensor = horus.Node(
name="sensor",
pubs=["temperature"],
tick=sensor_tick,
rate=10
)
horus.run(sensor)
Rust: Full framework capabilities for performance-critical code.
C++: C++17 bindings (horus_cpp) — Node, Scheduler, and Publisher/Subscriber over the same shared-memory topics. Scaffold with horus new --cpp my_project. See the C++ API Reference.
Rust, Python, and C++ nodes can communicate in the same application.
Core Concepts
Nodes
A Node is a component that performs a specific task. Examples:
- Read data from a sensor
- Process information
- Control a motor
- Display data on screen
- Monitor system health
Node lifecycle:
- init() - Start up (optional, run once)
- tick() - Do work (runs repeatedly)
- shutdown() - Clean up (optional, run once)
impl Node for MySensor {
fn name(&self) -> &str { "MySensor" }
fn init(&mut self) -> Result<()> {
hlog!(info, "Sensor starting up");
Ok(())
}
fn tick(&mut self) {
// Read sensor, send data - runs repeatedly
}
fn shutdown(&mut self) -> Result<()> {
hlog!(info, "Sensor shutting down");
Ok(())
}
}
Topics
A Topic is a named channel for sending messages. Multiple publishers can send to a topic, and multiple subscribers can receive from it.
Topic naming conventions:
- Use descriptive names:
"temperature","camera.image","motor.speed" - Use dots for hierarchy:
"sensors.imu.accel","actuators.left_wheel"
// Node A publishes temperature
let pub_a: Topic<f32> = Topic::new("temperature")?;
pub_a.send(25.0);
// Node B also publishes temperature
let pub_b: Topic<f32> = Topic::new("temperature")?;
pub_b.send(30.0);
// Node C receives from both
let sub: Topic<f32> = Topic::new("temperature")?;
if let Some(temp) = sub.recv() {
println!("Got: {}", temp);
}
Type safety: The type parameter (<f32>) ties the topic to one message type. Within a single program the compiler enforces it. Across nodes, processes or languages there is no compile step to catch a disagreement, so it is enforced when the topic is opened: the second process to open "temp" with a different type gets Failed to create topic 'temp': type mismatch.
Note: HORUS supports both typed messages (Pose2D, CmdVel, etc.) and generic messages (dicts/JSON in Python). Typed messages provide better performance and type safety. See Message Types for details.
Scheduler
The Scheduler runs nodes in priority order:
let mut scheduler = Scheduler::new();
// Add nodes with order (lower number = runs first)
scheduler.add(SensorNode::new()?).order(0).done(); // Runs first
scheduler.add(ProcessNode::new()?).order(1).done(); // Runs second
scheduler.add(DisplayNode::new()?).order(2).done(); // Runs third
// Run (Ctrl+C to stop)
scheduler.run()?;
Execution order: .order() is a fallback, not an absolute rule. As soon as any node in
the program has pub/sub topic metadata, the scheduler builds a dependency graph from it and
dispatches from the graph — a subscriber runs after its publisher even when it has the lower
.order(). When no node registered a topic during init() — the usual case — .order() tiers decide the sequence instead:
all order 0 nodes, then all order 1, then all order 2, repeat. See
Execution Classes.
Use order to control data flow:
- Sensors should run before processors (lower order number)
- Processors should run before actuators
- Safety checks should run first (order 0)
When to Use HORUS
Suitable Applications
Multi-component applications - Isolated components that communicate:
- Robot control systems
- Real-time data processing pipelines
- Multi-sensor fusion systems
- Parallel processing workflows
Real-time systems - When latency matters:
- Control loops (motor control, flight control)
- High-frequency data processing
- Live audio/video processing
Single-machine distributed systems - Multiple processes on one machine:
- Embedded Linux systems (Raspberry Pi, Jetson)
- Edge computing devices
- Multi-core applications
Multi-machine on a LAN - The opt-in net feature mirrors topics across machines over UDP with the same Topic<T> API (see Network Backends):
- Multi-robot fleets on one network
- Offloading compute to a workstation on the same subnet
Hardware integration - Combining multiple devices/languages:
- Mix Rust (performance) + Python (ease of use)
- Integrate Python prototypes with production Rust
Less Suitable Applications
Simple single-script programs - If your program fits in 100 lines, HORUS adds unnecessary complexity.
Wide-area / internet-scale distribution - HORUS LAN replication discovers peers with UDP multicast on a single subnet; it is not a WAN transport. For communication across the internet, use gRPC, HTTP, or message queues.
CRUD web applications - Use web frameworks (Axum, Actix, Django, Flask) instead.
Bare-metal embedded systems - HORUS requires an operating system with shared memory support. For microcontrollers, use RTIC or Embassy.
Comparison with Other Frameworks
vs Monolithic Programs
Traditional approach:
fn main() {
loop {
let temp = read_sensor();
let filtered = process(temp);
display(filtered);
}
}
Issues:
- Difficult to test individual parts
- Changes can break everything
- Components cannot be reused
- No parallelization
HORUS approach:
// SensorNode - reusable, testable, independent
struct SensorNode { data_pub: Topic<f32> }
// ProcessNode - can swap implementation
struct ProcessNode { data_sub: Topic<f32>, processed_pub: Topic<f32> }
// DisplayNode - can replace with LogNode, etc.
struct DisplayNode { data_sub: Topic<f32> }
Benefits:
- Test each node independently
- Change one node without affecting others
- Reuse nodes across projects
- Nodes can run in parallel
vs ROS (Robot Operating System)
| Aspect | HORUS | ROS |
|---|---|---|
| Typical latency | ~0.151 µs (one-way cross-process) | ~5 µs (ROS 2 default DDS, REP 2014) |
| Configuration | Code-based | XML files |
| Target use case | Single-machine performance + LAN replication | Multi-machine robotics |
| Ecosystem | Growing | Large |
Use ROS when: You need extensive robotics libraries, or communication beyond a local network.
Use HORUS when: You need high performance on a single machine, or across a few machines on the same local network.
vs Message Queues (RabbitMQ, Kafka)
| Aspect | HORUS | Message Queues |
|---|---|---|
| Latency | ~0.151 µs (one-way cross-process) | 1-10 ms |
| Scope | Single machine + LAN | Multi-machine |
| Configuration | Minimal | Complex |
| Persistence | No | Yes |
Use message queues when: You need WAN/internet-scale communication, persistence, or reliability guarantees.
Use HORUS when: You need high speed on a single machine or local network.
Architecture Overview
Data flow:
- Nodes communicate via Topics
- Topics write/read from shared memory
- Scheduler orchestrates node execution
- Monitor displays system status in real-time
Technical Details
Performance Characteristics
IPC Latency: every topic is cross-process shared memory, and HORUS picks the backend automatically from the topic's publisher/subscriber counts and whether the message type is plain data.
- Uncontended 1:1 — 20 ns same-thread, 63 ns cross-thread, 151 ns cross-process
- One publisher to many subscribers — 74 ns cross-thread, 191 ns cross-process
- Contended multi-producer — 226 ns same-process, 279 ns cross-process
- All send-only (one direction), on an Intel i7-10750H; the raw shared-memory floor on the same machine is 79 ns, so cross-process 1:1 pays 72 ns of framework overhead — see Benchmarks
Throughput:
- Small messages (<1KB): 2M+ msgs/sec
- Large messages (1MB): Limited by memory bandwidth
Memory Usage:
- Framework overhead: ~2MB
- Per topic: Auto-sized based on message type (~5KB to ~8MB)
- Per node: Depends on implementation
Built in Rust
HORUS leverages Rust for:
Safety - Compile-time guarantees:
- No null pointer dereferences
- No data races
- No use-after-free bugs
Performance - Zero-cost abstractions:
- No garbage collection pauses
- Predictable memory layout
- LLVM optimizations
Concurrency - Fearless concurrency:
- Send/Sync traits prevent data races
- Ownership prevents sharing mutable state
Learning Path
Start here:
- Installation - Get HORUS installed
- Quick Start - Build your first app
- Basic Examples - Working examples
Core concepts: 4. Nodes - Build components 5. Topic - Pub/sub communication 6. Scheduler - Run your application
Practical features: 7. node! Macro - Reduce boilerplate 8. Monitor - Monitor and debug
Advanced: 9. Multi-Language - Python and C++ integration 10. Performance - Optimization 11. Examples - Real projects
Next Steps
- Install HORUS - Get up and running
- Quick Start Tutorial - Build your first application
- See Examples - Learn from real projects
For command reference, see CLI Reference. For architecture details, see Architecture.