Choosing a Language
HORUS supports Rust, Python, and C++ — all three share the same topics over shared memory. This guide helps you choose the right one for your project.
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 an existing C++ codebase to integrate with
- You depend on vendor SDKs that only ship C++ headers
- You want native performance without adopting Rust
- Your team already knows C++
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",
pubs=["temperature"],
tick=sensor_tick
)
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) -> &'static 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(0).build()?;
scheduler.run()
}
The trait form above is the canonical Rust style — it is what
Quick Start teaches and what horus new
scaffolds by default. node! is a shorter spelling of the same thing, available
via horus new --macro; the generated constructor is infallible, so there is no
? on TempSensor::new():
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(0).build()?;
scheduler.run()
}
C++:
#include <horus/horus.hpp>
using namespace horus::literals;
class TempSensor : public horus::Node {
public:
TempSensor() : Node("TempSensor") {
temp_ = advertise<horus::msg::Temperature>("temperature");
}
void tick() override {
horus::msg::Temperature t{};
t.temperature = 25.0; // Read sensor
temp_->send(t);
}
private:
horus::Publisher<horus::msg::Temperature>* temp_;
};
int main() {
horus::Scheduler sched;
sched.tick_rate(100_hz).name("TempSensor");
TempSensor sensor;
sched.add(sensor).order(0).build();
sched.spin();
return 0;
}
Detailed Comparison
| Aspect | Python | Rust | C++ |
|---|---|---|---|
| Learning curve | Easy | Steeper | Steep, but familiar if you already write C++ |
| Setup time | 5 minutes | 10 minutes | 10 minutes (needs CMake and a C++17 compiler) |
| Compile time | None | A few seconds | A few seconds |
| Runtime performance | Good | Excellent | Excellent |
| Memory safety | Runtime checks | Compile-time guarantees | Manual (RAII, move-only handles, no borrow checker) |
| ML/AI integration | Excellent (numpy, torch, etc.) | Limited | Limited |
| Debugging | Simple print debugging | More tooling needed | gdb/lldb and sanitizers |
| Production readiness | Good for prototypes | Production-grade | Production-grade |
Performance Comparison
| Operation | Python | Rust | Difference |
|---|---|---|---|
| Node tick overhead | ~1.9ms/tick (~530 Hz, GIL-bound; the Rust binding itself is ~30μs) | Not benchmarked | — |
| Message send (typed) | ~1.5μs | ~91ns same-process / ~171ns cross-process | ~9-16x faster |
| Control loop (1kHz) | No — above the measured ~530 Hz tick ceiling | Easy | — |
| Control loop (10kHz) | No | Achievable | — |
Python figures come from horus_py/benchmarks/README.md (Python 3.12, Linux x86_64, WSL2); Rust figures from the performance table in the project README.md (Intel i9-14900K). These are different machines, so treat the ratio as indicative rather than exact. C++ has no column here because it is not separately benchmarked — the published tables cover Rust and Python only. C++ nodes reach the same shared-memory topics through the same core, but no measured C++ number exists to quote.
Bottom line: For most robotics applications, all three are fast enough. Rust or C++ matters when you need:
- Control loops faster than Python's measured ~530 Hz tick ceiling
- Hard real-time guarantees
- Minimal memory footprint
When to Choose Python
Rapid Prototyping
# Quick experiment - try different approaches fast
import horus
def experimental_tick(node):
# Easy to modify and test
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)
controller = horus.Node(
name="ExperimentalController",
subs=["sensor"],
pubs=["output"],
tick=experimental_tick
)
Machine Learning Integration
import torch
import horus
# Load model once at startup
model = torch.load("my_model.pt")
def ml_tick(node):
sensor_data = node.recv("sensor_data")
if sensor_data is not None:
# Easy integration with PyTorch
with torch.no_grad():
output = model(torch.tensor(sensor_data))
node.send("control_output", output.item())
ml_node = horus.Node(
name="MLController",
subs=["sensor_data"],
pubs=["control_output"],
tick=ml_tick
)
Education and Learning
Python's readable syntax makes it easier to understand robotics concepts without fighting the language.
When to Choose Rust
Production Deployments
// Rust catches bugs at compile time
enum SafetyCheck {
Ok,
Warning(String),
Critical(String),
}
impl Node for SafetyMonitor {
fn tick(&mut self) {
// Compiler ensures we handle all cases
match self.check_safety() {
SafetyCheck::Ok => self.continue_operation(),
SafetyCheck::Warning(msg) => self.log_warning(&msg),
SafetyCheck::Critical(msg) => self.emergency_stop(&msg),
}
}
}
High-Frequency Control
// 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
When to Choose C++
Existing C++ Code and Vendor SDKs
A HORUS C++ node is an ordinary C++ class, so a driver, planner or vendor SDK you already
have links straight into tick() — no wrapper process and no bridge node in between.
#include <horus/horus.hpp>
#include "vendor_sdk/lidar.hpp" // your existing header, unchanged
class LidarNode : public horus::Node {
public:
LidarNode() : Node("lidar") {
scan_ = advertise<horus::msg::LaserScan>("lidar.scan");
}
void tick() override {
horus::msg::LaserScan s{};
vendor_.read_into(s.ranges); // call directly into the vendor SDK
scan_->send(s);
}
private:
VendorLidar vendor_;
horus::Publisher<horus::msg::LaserScan>* scan_;
};
Native Performance Without Adopting Rust
The C++ API is a set of C++17 headers over an extern "C" FFI into the same core, so C++
nodes publish and subscribe on the same shared-memory topics as Rust and Python nodes.
You get a compiled, no-GIL node without retraining a team on Rust. (There are no
separately published C++ latency figures — see the note under the performance table.)
What It Costs
C++ gives you none of Rust's compile-time memory-safety guarantees: lifetimes are yours to
manage, and Publisher/Subscriber handles are move-only but not borrow-checked. You also
need CMake and a C++17 compiler in the build environment, where Python needs neither.
Mixed Language Projects
You can use all three 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:
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",
subs=["camera"],
pubs=["detections"],
tick=detector_tick,
rate=10
)
Rust control node:
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 | Python or Rust |
| Machine learning robot | Python + Rust |
| Industrial automation | Rust |
| Drone/UAV | Rust |
| Research prototype | Python |
| Competition robot | Rust |
| Product development | Rust |
| Existing C++ codebase or vendor SDK | C++ |
Getting Started
Ready to start with Python?
- Python Guide (uses Python)
- Python API Reference
Ready to start with Rust?
- Quick Start (uses Rust)
- node! Macro Guide
- Rust API Reference
Ready to start with C++?
- C++ API Reference (single include:
#include <horus/horus.hpp>) - Scaffold a project with
horus new --cpp(or pick option 3 in the interactivehorus newprompt)
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.