Choosing a Language
HORUS supports Rust, Python, and C++. All three share the same shared-memory IPC — a Rust node can publish to a C++ subscriber with zero overhead. This guide helps you choose.
Prerequisites
- HORUS installed (Installation Guide)
Quick Decision
Use Python if:
- You're prototyping or experimenting
- You're new to robotics programming
- You want to integrate with ML/AI libraries (TensorFlow, PyTorch)
- Development speed matters more than runtime performance
Use Rust if:
- You need maximum performance
- You're building production systems
- You want compile-time safety guarantees
- You're comfortable with Rust (or want to learn)
Use C++ if:
- You have existing C++ codebases or libraries to integrate
- Your team already knows C++ (common in robotics, automotive, gaming)
- You're migrating from ROS2 C++ (rclcpp)
- You need direct hardware access with familiar tooling (CMake, GDB)
- You want near-Rust performance without learning Rust
Side-by-Side Comparison
Hello World: Temperature Sensor
Python:
import horus
def sensor_tick(node):
temp = 25.0 # Read sensor
node.send("temperature", temp)
sensor = horus.Node(name="TempSensor", tick=sensor_tick, order=5,
pubs=["temperature"])
horus.run(sensor)
Rust:
use horus::prelude::*;
struct TempSensor {
pub_topic: Topic<f32>,
}
impl TempSensor {
fn new() -> Result<Self> {
Ok(Self { pub_topic: Topic::new("temperature")? })
}
}
impl Node for TempSensor {
fn name(&self) -> &str { "TempSensor" }
fn tick(&mut self) {
let temp = 25.0; // Read sensor
self.pub_topic.send(temp);
}
}
fn main() -> Result<()> {
let mut scheduler = Scheduler::new();
scheduler.add(TempSensor::new()?).order(5).build()?;
scheduler.run()
}
Or with the Rust node! macro:
use horus::prelude::*;
node! {
TempSensor {
pub { temperature: f32 -> "temperature" }
tick {
let temp = 25.0;
self.temperature.send(temp);
}
}
}
fn main() -> Result<()> {
let mut scheduler = Scheduler::new();
scheduler.add(TempSensor::new()).order(5).build()?;
scheduler.run()
}
C++:
#include <horus/horus.hpp>
int main() {
horus::Scheduler sched;
horus::Publisher<horus::msg::CmdVel> pub("temperature");
sched.add("TempSensor")
.order(5)
.tick([&] {
auto sample = pub.loan(); // zero-copy SHM
sample->linear = 25.0f; // read sensor
pub.publish(std::move(sample));
})
.build();
sched.spin();
}
Detailed Comparison
| Aspect | Python | Rust | C++ |
|---|---|---|---|
| Learning curve | Easy | Steeper | Moderate |
| Setup time | 5 minutes | 10 minutes | 10 minutes |
| Compile time | None | A few seconds | A few seconds |
| Runtime performance | Good | Excellent | Excellent |
| Memory safety | Runtime checks | Compile-time guarantees | Manual (RAII helps) |
| ML/AI integration | Excellent (numpy, torch) | Limited | Good (OpenCV, TensorRT) |
| Debugging | Simple print | More tooling | GDB, Valgrind, ASAN |
| Production readiness | Good for prototypes | Production-grade | Production-grade |
| ROS2 migration | Easy | Moderate | Easiest (similar API) |
| IPC latency | ~80ns send | ~11ns send | ~15ns send (FFI overhead) |
Performance Comparison
| Operation | Python | Rust | Difference |
|---|---|---|---|
| Node tick latency | ~10μs | ~1μs | 10x faster |
| Message send | ~2μs | ~400ns | 5x faster |
| Control loop (1kHz) | Achievable | Easy | - |
| Control loop (10kHz) | Difficult | Achievable | - |
Bottom line: For most robotics applications, both are fast enough. Rust matters when you need very high-frequency control (>1kHz), hard real-time guarantees, or minimal memory footprint.
Scheduling Features
Both Rust and Python support the full scheduling API:
| Feature | Rust | Python | Notes |
|---|---|---|---|
.rate() | Yes | Yes | Tick rate in Hz |
.order() | Yes | Yes | Execution priority |
.budget() | Yes | Yes | Tick time budget |
.deadline() | Yes | Yes | Hard deadline |
.on_miss() | Yes | Yes | Deadline miss policy |
.priority() | Yes | Yes | OS thread priority (SCHED_FIFO) |
.core() | Yes | Yes | CPU core pinning |
.watchdog() | Yes | Yes | Per-node watchdog timeout |
.compute() | Yes | Yes | CPU-bound thread pool |
.async_io() | Yes | Yes | I/O-bound async pool |
.on(topic) | Yes | Yes | Event-triggered execution |
enter_safe_state() | Yes | No | Requires implementing Node trait |
Performance difference: While both languages expose the same API, Rust nodes achieve lower tick jitter and more predictable timing due to the absence of the GIL. For control loops above 1kHz with hard deadlines, Rust is recommended.
Best practice: Use Rust for RT-critical driver nodes (motors, safety). Use Python for application logic (planning, ML inference, behavior trees). They communicate via shared memory topics — zero overhead across the language boundary.
When to Choose Python
Rapid Prototyping
# Quick experiment - try different approaches fast
import horus
def controller_tick(node):
input_val = node.recv("sensor") or 0.0
strategy = "aggressive"
if strategy == "aggressive":
output = input_val * 2.0
else:
output = input_val * 0.5
node.send("output", output)
ctrl = horus.Node(name="ExperimentalController", tick=controller_tick,
subs=["sensor"], pubs=["output"])
Machine Learning Integration
import torch
import horus
model = torch.load("my_model.pt")
def ml_tick(node):
sensor_data = node.recv("sensor_data")
if sensor_data is not None:
with torch.no_grad():
output = model(torch.tensor(sensor_data))
node.send("control_output", output.item())
ml_node = horus.Node(name="MLController", tick=ml_tick,
subs=["sensor_data"], pubs=["control_output"])
Education and Learning
Python's readable syntax makes it easier to understand robotics concepts without fighting the language.
When to Choose Rust
Production Deployments
// simplified
// Rust catches bugs at compile time
impl Node for SafetyMonitor {
fn tick(&mut self) {
// Compiler ensures we handle all cases
match self.check_safety() {
SafetyStatus::OK => self.continue_operation(),
SafetyStatus::Warning(msg) => self.log_warning(&msg),
SafetyStatus::Critical(msg) => self.emergency_stop(&msg),
}
}
}
High-Frequency Control
// simplified
// Rust can sustain 10kHz+ control loops
impl Node for MotorController {
fn tick(&mut self) {
// Microsecond-level timing is reliable
let error = self.target - self.position;
let output = self.pid.compute(error);
self.motor.send(output);
}
}
Resource-Constrained Environments
// Rust has minimal runtime overhead
// Perfect for embedded systems and single-board computers
Mixed Language Projects
You can use both languages in the same project! HORUS nodes communicate via shared memory, which works across languages.
Example: Python for AI, Rust for control
Python ML node:
import horus
def detector_tick(node):
camera_image = node.recv("camera")
if camera_image is not None:
detections = model.detect(camera_image)
node.send("detections", detections)
detector = horus.Node(name="ObjectDetector", tick=detector_tick,
subs=["camera"], pubs=["detections"])
horus.run(detector)
Rust control node:
// simplified
impl Node for NavigationController {
fn tick(&mut self) {
if let Some(detections) = self.detection_sub.recv() {
// React to Python node's output
self.plan_path(&detections);
}
}
}
Recommendation by Use Case
| Use Case | Recommended Language |
|---|---|
| Learning HORUS | Python |
| University project | Python |
| Hobby robot | Either |
| Machine learning robot | Python + Rust |
| Industrial automation | Rust |
| Drone/UAV | Rust |
| Research prototype | Python |
| Competition robot | Rust |
| Product development | Rust |
Getting Started
Ready to start with Python?
Ready to start with Rust?
- Quick Start (uses Rust)
- node! Macro Guide
- Rust API Reference
Still Unsure?
Start with Python. It's faster to get something working, and you can always port critical parts to Rust later. HORUS makes it easy to mix languages.
See Also
- Quick Start (Rust) — First Rust app
- Quick Start (Python) — First Python app
- Multi-Language Support — How Rust and Python interoperate