Multi-Language Support
HORUS supports multiple programming languages, allowing you to choose the best tool for each component of your robotics system.
Supported Languages
Rust (Native)
Best for: High-performance nodes, control loops, real-time systems
HORUS is written in Rust and provides the most complete API. All examples in the documentation use Rust by default.
Getting Started:
horus new my-project
# Select: Rust (option 2)
Learn more: Quick Start
Python
Production-Ready: Full-featured Python API with advanced capabilities
Best for: Rapid prototyping, AI/ML integration, data processing, visualization
Python bindings (via PyO3) provide a simple, Pythonic API for HORUS with production-grade features:
- Per-node rate control - Different rates for different nodes (100Hz sensor, 10Hz logger)
- Automatic timestamps - Built-in message timing and staleness detection
- Typed messages - Type-safe messages shared with Rust (CmdVel, Pose2D, Imu, Odometry, LaserScan)
- Generic messages - Send any Python type (dicts, lists, etc.) between Python nodes
- Multiprocess support - Process isolation via shared memory
Perfect for integrating with NumPy, PyTorch, TensorFlow, and other Python libraries.
Getting Started:
horus new my-project
# Select: Python (option 1)
Learn more: Python Bindings
Cross-Language Communication
Python and Rust nodes communicate through HORUS's shared memory system. For cross-language communication, both sides must use the same typed message types.
Typed Topics for Cross-Language Communication
Pass a message type class to the Python Topic() constructor to create a typed topic that Rust can read:
# Python publisher (pose_publisher.py)
from horus import Topic, Pose2D
# Create typed topic - the type determines the topic name and serialization
topic = Topic(Pose2D)
# Send a Pose2D message (shared memory, readable by Rust)
pose = Pose2D(x=1.0, y=2.0, theta=0.5)
topic.send(pose)
// Rust subscriber (in another process)
use horus::prelude::*;
let topic: Topic<Pose2D> = Topic::new("pose")?;
if let Some(pose) = topic.recv() {
println!("Received: x={}, y={}, theta={}", pose.x, pose.y, pose.theta);
}
Supported Typed Message Types
These types work for Python-Rust cross-language communication:
| Message Type | Python Constructor | Default Topic Name | Use Case |
|---|---|---|---|
CmdVel | CmdVel(linear, angular) | cmd_vel | Velocity commands |
Pose2D | Pose2D(x, y, theta) | pose | 2D position |
Imu | Imu(accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z) | imu | IMU sensor data |
Odometry | Odometry(x, y, theta, linear_velocity, angular_velocity) | odom | Odometry data |
LaserScan | LaserScan(angle_min, angle_max, ..., ranges=[...]) | scan | LiDAR scans |
All message types include an optional timestamp_ns field for nanosecond timestamps.
Usage examples:
from horus import Topic, CmdVel, Imu, LaserScan
# Velocity commands
cmd_topic = Topic(CmdVel)
cmd_topic.send(CmdVel(linear=1.5, angular=0.3))
# IMU data
imu_topic = Topic(Imu)
imu_topic.send(Imu(
accel_x=0.0, accel_y=0.0, accel_z=9.81,
gyro_x=0.0, gyro_y=0.0, gyro_z=0.1
))
# Receive (returns typed Python object or None)
if cmd := cmd_topic.recv():
print(f"linear={cmd.linear}, angular={cmd.angular}")
Generic Topics (Python-to-Python Only)
For Python-to-Python communication, pass a string name to create a generic topic that can send any Python type:
from horus import Topic
# Generic topic - pass topic name as string
topic = Topic("my_data")
topic.send({"sensor": "lidar", "ranges": [1.0, 1.1, 1.2]})
topic.send([1, 2, 3])
topic.send("hello")
# Receive
if msg := topic.recv():
print(msg) # Python dict, list, string, etc.
Generic topics use JSON/MessagePack serialization internally. Rust nodes cannot read generic topics — use typed messages for cross-language communication.
When to use which:
- Typed Topics (
Topic(CmdVel),Topic(Pose2D)) — Cross-language Rust+Python or Python-only - Generic Topics (
Topic("topic_name")) — Python-only systems with custom data
Python Node API
The Python Node class provides a simple callback-based API:
from horus import Node, Topic, Scheduler, CmdVel, Pose2D
class Controller(Node):
def __init__(self):
super().__init__("Controller")
def init(self):
self.pose_sub = Topic(Pose2D)
self.cmd_pub = Topic(CmdVel)
def tick(self):
pose = self.pose_sub.recv()
if pose is not None:
# Compute velocity command from pose
cmd = CmdVel(linear=1.0, angular=0.5)
self.cmd_pub.send(cmd)
scheduler = Scheduler()
scheduler.node(Controller()).order(0).rate(30).build()
scheduler.run()
Key Topic methods:
topic.send(msg)— Send data to a topictopic.recv()— Get next message (returnsNoneif no messages)
Choosing a Language
| Use Case | Recommended Language |
|---|---|
| Control loops | Rust (lowest latency) |
| AI/ML models | Python (ecosystem) |
| Hardware drivers | Rust |
| Data processing | Python or Rust |
| Real-time systems | Rust |
| Prototyping | Python (fastest development) |
Mixed-Language Systems
You can build systems with nodes in different languages:
Example: Robot with mixed languages
- Motor controller (Rust) — 1kHz control loop
- Vision processing (Python) — PyTorch object detection
- Hardware driver (Rust) — Sensor integration
- Monitor (Rust) — Real-time visualization
All communicate through HORUS shared memory with sub-microsecond latency.
Running Mixed-Language Systems
The horus run command automatically handles compilation and execution of mixed-language systems:
# Mix Python and Rust nodes
horus run sensor.py controller.rs visualizer.py
# Mix Rust and Python
horus run lidar_driver.rs planner.py motor_control.rs
What happens:
- Rust files (
.rs) are automatically compiled withcargo buildusing HORUS dependencies - Python files (
.py) are executed directly with Python 3 - All processes communicate via shared memory at
/dev/shm/horus/ horus runmanages the lifecycle (start, monitor, stop all together)
Note: For Rust files, horus run creates a temporary Cargo project in .horus/ with proper dependencies, builds it with cargo build, and executes the resulting binary.
Example: Complete Mixed System
Python sensor node (sensor.py):
from horus import Node, Topic, Scheduler, LaserScan
class LidarSensor(Node):
def __init__(self):
super().__init__("LidarSensor")
def init(self):
self.scan_pub = Topic(LaserScan)
def tick(self):
scan = LaserScan(
angle_min=-1.57,
angle_max=1.57,
angle_increment=0.01,
range_min=0.1,
range_max=10.0,
ranges=[1.0, 1.1, 1.2, 0.9]
)
self.scan_pub.send(scan)
scheduler = Scheduler()
scheduler.node(LidarSensor()).order(0).rate(10).build()
scheduler.run()
Rust planner node (planner.rs):
use horus::prelude::*;
fn main() -> Result<()> {
let scan_topic: Topic<LaserScan> = Topic::new("scan")?;
let cmd_topic: Topic<CmdVel> = Topic::new("cmd_vel")?;
loop {
if let Some(scan) = scan_topic.recv() {
let cmd = plan_path(&scan); // Your planning logic
cmd_topic.send(cmd);
}
}
}
# Run both together
horus run sensor.py planner.rs
# Both processes communicate via shared memory
Benefits:
- No manual compilation —
horus runhandles it - Automatic dependency management — HORUS libraries linked correctly
- Process isolation — One crash doesn't kill the whole system
- True parallelism — Each process can use separate CPU cores
API Parity
| Feature | Rust | Python |
|---|---|---|
| Topic send/recv | topic.send(msg) / topic.recv() | topic.send(msg) / topic.recv() |
| Typed messages | Topic<CmdVel> | Topic(CmdVel) |
| Generic messages | Topic<GenericMessage> | Topic("name") |
| Node lifecycle | init(), tick(), shutdown() | init(), tick(), shutdown() callbacks |
| Scheduler | Scheduler::new() | Scheduler() |
| Node priority | .order(n) | order=n |
| Rate control | Scheduler rate | rate=Hz per node |
| Backend hints | Automatic (topology-based) | backend="spsc" kwarg (hint) |
| Message types | Full horus_library | CmdVel, Pose2D, Imu, Odometry, LaserScan + horus.library |
| TransformFrame transforms | TransformFrame::new() | TransformFrame() |
| Tensor system | Native | Image, PointCloud, DepthImage (pool-backed) |
| Logging | hlog!(info, ...) | node.log_info(...) |
Next Steps
Choose your language:
- Python Bindings — Full guide with examples
- Quick Start — Get started with Rust
Build something:
- Examples — See multi-language systems in action
- CLI Reference —
horus newcommand options