{"title":"Multi-Language Support","description":"Use HORUS with Rust, Python, and C++","slug":"concepts/multi-language","content":"# Multi-Language Support\n\nHORUS supports **Rust**, **Python**, and **C++**. All three share the same shared-memory IPC — a C++ publisher writes directly to the same ring buffer a Python subscriber reads from. Zero serialization, zero copying.\n\n## Supported Languages\n\n### Rust (Native)\n\n**Best for:** High-performance nodes, control loops, real-time systems\n\nHORUS is written in Rust and provides the most complete API.\n\n```bash\nhorus new my-project        # Rust by default\n```\n\n**Learn more:** [Quick Start](/getting-started/quick-start)\n\n### Python\n\n**Best for:** Rapid prototyping, AI/ML integration, data processing, visualization\n\nPython bindings (via PyO3) provide a Pythonic API with typed messages, per-node rate control, and multiprocess support. Integrates with NumPy, PyTorch, TensorFlow.\n\n```bash\nhorus new my-project --python\n```\n\n**Learn more:** [Python Quick Start](/getting-started/quick-start-python)\n\n### C++\n\n**Best for:** Existing C++ codebases, ROS2 migration, hardware drivers, performance-critical systems\n\nC++ bindings provide an idiomatic API with RAII, move semantics, zero-copy loan pattern, and template specializations. 15ns FFI overhead. Full API: Scheduler, Publisher/Subscriber, TensorPool, Image, PointCloud, Params, Services, Actions, TransformFrame.\n\n```bash\nhorus new my-project --cpp\n```\n\n**Learn more:** [C++ Quick Start](/getting-started/quick-start-cpp) | [Migrating from ROS2 C++](/tutorials/migrating-from-ros2-cpp)\n\n## Cross-Language Communication\n\nAll three languages communicate through HORUS's shared memory system. For cross-language communication, all sides **must use the same typed message types**. The SHM ring buffer layout is identical regardless of which language writes to it.\n\n### Typed Topics for Cross-Language Communication\n\nPass a message type class to the Python `Topic()` constructor to create a typed topic that Rust can read:\n\n\n### Supported Typed Message Types\n\nThese types work for Python-Rust cross-language communication:\n\n| Message Type | Python Constructor | Default Topic Name | Use Case |\n|-------------|-------------------|-------------------|----------|\n| `CmdVel` | `CmdVel(linear, angular)` | `cmd_vel` | Velocity commands |\n| `Pose2D` | `Pose2D(x, y, theta)` | `pose` | 2D position |\n| `Imu` | `Imu(accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z)` | `imu` | IMU sensor data |\n| `Odometry` | `Odometry(x, y, theta, linear_velocity, angular_velocity)` | `odom` | Odometry data |\n| `LaserScan` | `LaserScan(angle_min, angle_max, ..., ranges=[...])` | `scan` | LiDAR scans |\n\nAll message types include an optional `timestamp_ns` field for nanosecond timestamps.\n\n**Usage examples:**\n\n```python\nfrom horus import Topic, CmdVel, Imu, LaserScan\n\n# Velocity commands\ncmd_topic = Topic(CmdVel)\ncmd_topic.send(CmdVel(linear=1.5, angular=0.3))\n\n# IMU data\nimu_topic = Topic(Imu)\nimu_topic.send(Imu(\n    accel_x=0.0, accel_y=0.0, accel_z=9.81,\n    gyro_x=0.0, gyro_y=0.0, gyro_z=0.1\n))\n\n# Receive (returns typed Python object or None)\nif cmd := cmd_topic.recv():\n    print(f\"linear={cmd.linear}, angular={cmd.angular}\")\n```\n\n### Generic Topics (Python-to-Python Only)\n\nFor Python-to-Python communication, pass a string name to create a generic topic that can send any Python type:\n\n```python\nfrom horus import Topic\n\n# Generic topic - pass topic name as string\ntopic = Topic(\"my_data\")\ntopic.send({\"sensor\": \"lidar\", \"ranges\": [1.0, 1.1, 1.2]})\ntopic.send([1, 2, 3])\ntopic.send(\"hello\")\n\n# Receive\nif msg := topic.recv():\n    print(msg)  # Python dict, list, string, etc.\n```\n\nGeneric topics use JSON/MessagePack serialization internally. **Rust nodes cannot read generic topics** — use typed messages for cross-language communication.\n\n**When to use which:**\n- **Typed Topics** (`Topic(CmdVel)`, `Topic(Pose2D)`) — Cross-language Rust+Python or Python-only\n- **Generic Topics** (`Topic(\"topic_name\")`) — Python-only systems with custom data\n\n## Python Node API\n\nThe Python `Node` class provides a simple callback-based API:\n\n```python\nimport horus\nfrom horus import CmdVel, Pose2D, Topic\n\npose_sub = Topic(Pose2D)\ncmd_pub = Topic(CmdVel)\n\ndef controller_tick(node):\n    pose = pose_sub.recv()\n    if pose is not None:\n        # Compute velocity command from pose\n        cmd = CmdVel(linear=1.0, angular=0.5)\n        cmd_pub.send(cmd)\n\ncontroller = horus.Node(name=\"Controller\", tick=controller_tick,\n                        order=0, rate=30,\n                        subs=[\"pose\"], pubs=[\"cmd_vel\"])\nhorus.run(controller)\n```\n\n**Key Topic methods:**\n- `topic.send(msg)` — Send data to a topic\n- `topic.recv()` — Get next message (returns `None` if no messages)\n\n## Choosing a Language\n\n| Use Case | Recommended Language |\n|----------|---------------------|\n| **Control loops** | Rust (lowest latency) |\n| **AI/ML models** | Python (ecosystem) |\n| **Hardware drivers** | Rust |\n| **Data processing** | Python or Rust |\n| **Real-time systems** | Rust |\n| **Prototyping** | Python (fastest development) |\n\n## Mixed-Language Systems\n\nYou can build systems with nodes in different languages:\n\n**Example: Robot with mixed languages**\n- **Motor controller** (Rust) — 1kHz control loop\n- **Vision processing** (Python) — PyTorch object detection\n- **Hardware driver** (Rust) — Sensor integration\n- **Monitor** (Rust) — Real-time visualization\n\nAll communicate through HORUS shared memory with **sub-microsecond latency**.\n\n### Running Mixed-Language Systems\n\nThe `horus run` command automatically handles compilation and execution of mixed-language systems:\n\n```bash\n# Mix Python and Rust nodes\nhorus run sensor.py controller.rs visualizer.py\n\n# Mix Rust and Python\nhorus run lidar_driver.rs planner.py motor_control.rs\n```\n\n**What happens:**\n1. **Rust files** (`.rs`) are automatically compiled with `cargo build` using HORUS dependencies\n2. **Python files** (`.py`) are executed directly with Python 3\n3. All processes communicate via shared memory (managed automatically by `horus_sys`)\n4. `horus run` manages the lifecycle (start, monitor, stop all together)\n\n**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.\n\n### Example: Complete Mixed System\n\n\n```bash\n# Run both together\nhorus run sensor.py planner.rs\n\n# Both processes communicate via shared memory\n```\n\n**Benefits:**\n- **No manual compilation** — `horus run` handles it\n- **Automatic dependency management** — HORUS libraries linked correctly\n- **Process isolation** — One crash doesn't kill the whole system\n- **True parallelism** — Each process can use separate CPU cores\n\n## API Parity\n\n| Feature | Rust | Python |\n|---------|------|--------|\n| **Topic send/recv** | `topic.send(msg)` / `topic.recv()` | `topic.send(msg)` / `topic.recv()` |\n| **Typed messages** | `Topic` | `Topic(CmdVel)` |\n| **Generic messages** | `Topic` | `Topic(\"name\")` |\n| **Node lifecycle** | `init()`, `tick()`, `shutdown()` | `init()`, `tick()`, `shutdown()` callbacks |\n| **Scheduler** | `Scheduler::new()` | `Scheduler()` |\n| **Node priority** | `.order(n)` | `order=n` |\n| **Rate control** | `Scheduler` rate | `rate=Hz` per node |\n| **Transport selection** | Automatic (topology-based) | Automatic (topology-based) |\n| **Message types** | `horus_types` + `horus-robotics` | CmdVel, Pose2D, Imu, Odometry, LaserScan + top-level Python `horus` exports |\n| **TransformFrame transforms** | `TransformFrame::new()` | `TransformFrame()` |\n| **Tensor system** | Native | `Image`, `PointCloud`, `DepthImage` (pool-backed) |\n| **Logging** | `hlog!(info, ...)` | `node.log_info(...)` |\n\n## Next Steps\n\n**Choose your language:**\n- [Python Bindings](/python/api/python-bindings) — Full guide with examples\n- [Quick Start](/getting-started/quick-start) — Get started with Rust\n\n**Build something:**\n- [Examples](/rust/examples/basic-examples) — See multi-language systems in action\n- [CLI Reference](/development/cli-reference) — `horus new` command options\n\n---\n\n## Design Decisions\n\n### Why Rust + Python (Not C++)\n\nROS2's primary languages are C++ and Python — C++ for performance, Python for scripting. HORUS chose Rust over C++ for the core because Rust provides the same zero-cost abstractions and bare-metal performance while eliminating entire categories of bugs (use-after-free, data races, buffer overflows) at compile time. For robotics — where memory safety bugs can cause physical harm — this is not a style preference but a safety requirement. Python was kept because the ML/AI ecosystem (PyTorch, TensorFlow, NumPy) is overwhelmingly Python-based, and robotics increasingly depends on learned models.\n\n### Why Same Topics Across Languages (Shared Memory)\n\nPython and Rust nodes communicate through the same shared memory topics with identical layouts. A `Topic(CmdVel)` in Python writes to the same shared memory region as `Topic` in Rust. This means cross-language communication has the same sub-microsecond latency as same-language communication — there is no serialization bridge, no socket layer, and no middleware translation. The tradeoff is that typed message layouts must match exactly across languages, which is enforced by generating the Python message classes from the same Rust struct definitions via PyO3.\n\n### Why PyO3 Bindings with a Python Wrapper Layer\n\nHORUS uses a two-layer Python architecture: `_horus` (Rust bindings via PyO3) and `horus` (Python wrapper in `__init__.py`). The Rust layer provides raw performance and shared memory access. The Python wrapper adds Pythonic ergonomics — keyword arguments, sensible defaults, `horus.run()` one-liner, and idiomatic naming. This separation means the Rust layer can expose low-level primitives without worrying about Python API design, while the Python layer can evolve its interface without changing Rust code. Users import `horus`, not `_horus`.\n\n## Trade-offs\n\n| Area | Benefit | Cost |\n|------|---------|------|\n| **Rust over C++** | Memory safety at compile time; no data races or use-after-free in production | Steeper learning curve for teams coming from C++; smaller ecosystem of robotics-specific Rust libraries |\n| **Shared memory across languages** | Sub-microsecond cross-language latency; no serialization bridge | Typed message layouts must match exactly — adding a field to a Rust message requires updating the Python binding |\n| **PyO3 bindings** | Direct access to Rust performance from Python; no FFI boilerplate | PyO3 builds require a Rust toolchain — pure-Python environments cannot use HORUS without compiling |\n| **Two-layer Python API** | Clean Pythonic interface independent of Rust internals | Two layers to maintain; changes to the Rust API may not automatically surface in the Python wrapper |\n| **Generic topics (Python-only)** | Send any Python type without defining a message struct | Not readable by Rust nodes; serialization overhead compared to typed topics |\n| **No C++ support** | Simpler codebase, fewer language bindings to maintain | Cannot integrate with existing C++ robotics code without an interop layer or rewrite |\n\n## Cross-Language IPC: Rust and Python Sharing Topics\n\nRust and Python nodes can communicate through the same shared memory topics. Both languages use the same binary wire format (POD zero-copy) when using **typed topics**.\n\n### Rust Publisher, Python Subscriber\n\n**Rust side:**\n```rust\n// simplified\nuse horus::prelude::*;\nuse horus_robotics::prelude::*;\nuse horus_robotics::Imu;\n\nlet topic: Topic<Imu> = Topic::new(\"sensor.imu\")?;\nlet mut imu = Imu::new();\nimu.linear_acceleration = [0.0, 0.0, 9.81];\ntopic.send(imu);\n```\n\n**Python side:**\n```python\nimport horus\n\ndef tick(node):\n    msg = node.recv(\"sensor.imu\")\n    if msg is not None:\n        print(f\"accel_z = {msg.linear_acceleration[2]}\")\n\nsub = horus.Node(\"py_sub\", tick=tick,\n                  subs={\"sensor.imu\": horus.Imu}, rate=100)\nhorus.run(sub, duration=10.0)\n```\n\nThe **dict key** `\"sensor.imu\"` becomes the SHM topic name. Both sides must use the same string. The **type** (`horus.Imu`) tells Python to use the POD zero-copy path — the same binary layout as Rust.\n\n### Python Publisher, Rust Subscriber\n\n**Python side:**\n```python\nimport horus\n\ndef tick(node):\n    cmd = horus.CmdVel(1.5, 0.5)  # linear, angular\n    node.send(\"motor.cmd\", cmd)\n\npub = horus.Node(\"py_pub\", tick=tick,\n                  pubs={\"motor.cmd\": horus.CmdVel}, rate=100)\nhorus.run(pub, duration=10.0)\n```\n\n**Rust side:**\n```rust\nlet topic: Topic<CmdVel> = Topic::new(\"motor.cmd\")?;\nif let Some(cmd) = topic.recv() {\n    println!(\"linear={}, angular={}\", cmd.linear, cmd.angular);\n}\n```\n\n### Requirements for Cross-Language IPC\n\n1. **Same topic name** on both sides (the string in `Topic::new()` and the Python dict key)\n2. **Typed topics in Python** — use `subs={\"name\": horus.Imu}`, not `subs=\"name\"` (string topics use a different wire format)\n3. **Same message type** — `Topic&lt;Imu&gt;` in Rust matches `horus.Imu` in Python\n\n### Supported Cross-Language Types\n\nAll 30+ typed message types work across Rust and Python:\n\n`CmdVel`, `Imu`, `Odometry`, `LaserScan`, `JointState`, `BatteryState`, `Pose2D`, `Pose3D`, `Twist`, `Vector3`, `Point3`, `Quaternion`, `MotorCommand`, `ServoCommand`, `DifferentialDriveCommand`, `Heartbeat`, `DiagnosticStatus`, `EmergencyStop`, `MagneticField`, `Temperature`, `FluidPressure`, `Illuminance`, `RangeSensor`, `NavSatFix`, `NavGoal`, `WrenchStamped`, `Clock`, `PidConfig`, `TrajectoryPoint`, `JointCommand`\n\n### What Does NOT Work Cross-Language\n\n- **Python generic topics** (`subs=\"my_topic\"` without a type) — these use Python-specific serialization that Rust cannot read\n- **Custom Rust serde types** — Python can only read the pre-defined POD types listed above\n- **Python dict messages** (`node.send(\"topic\", {\"x\": 1.0})`) — not readable by Rust\n\n## See Also\n\n- [Python Overview](/python) — Python documentation hub\n- [Choosing a Language](/getting-started/choosing-language) — Rust vs Python comparison\n- [Python Bindings](/python/api/python-bindings) — Python API reference","headings":["Multi-Language Support","Supported Languages","Rust (Native)","Python","C++","Cross-Language Communication","Typed Topics for Cross-Language Communication","Supported Typed Message Types","Velocity commands","IMU data","Receive (returns typed Python object or None)","Generic Topics (Python-to-Python Only)","Generic topic - pass topic name as string","Receive","Python Node API","Choosing a Language","Mixed-Language Systems","Running Mixed-Language Systems","Mix Python and Rust nodes","Mix Rust and Python","Example: Complete Mixed System","Run both together","Both processes communicate via shared memory","API Parity","Next Steps","Design Decisions","Why Rust + Python (Not C++)","Why Same Topics Across Languages (Shared Memory)","Why PyO3 Bindings with a Python Wrapper Layer","Trade-offs","Cross-Language IPC: Rust and Python Sharing Topics","Rust Publisher, Python Subscriber","Python Publisher, Rust Subscriber","Requirements for Cross-Language IPC","Supported Cross-Language Types","What Does NOT Work Cross-Language","See Also"],"wordCount":1855}