Getting Started with C++

HORUS provides idiomatic C++ bindings with zero-copy shared memory IPC, real-time scheduling, and the full message type system. The C++ API matches the Rust API's method names and patterns — if you know one, you know both.

Prerequisites

  • CMake 3.20+
  • GCC 12+ or Clang 15+
  • HORUS installed (curl -fsSL https://gitlab.com/softmata/horus/-/raw/release/install.sh | bash)

Quick Start

Create a new C++ project:

horus new my-robot --cpp
cd my-robot
horus run

This generates a working controller that publishes velocity commands:

#include <horus/horus.hpp>
using namespace horus::literals;

int main() {
    auto sched = horus::Scheduler()
        .tick_rate(100_hz)
        .name("my-robot");

    auto cmd_pub = sched.advertise<horus::msg::CmdVel>("cmd_vel");

    sched.add("controller")
        .rate(50_hz)
        .budget(5_ms)
        .on_miss(horus::Miss::Skip)
        .tick([&] {
            auto sample = cmd_pub.loan();
            sample->linear = 0.3f;
            sample->angular = 0.0f;
            cmd_pub.publish(std::move(sample));
        })
        .build();

    sched.spin();
}

Core Concepts

Scheduler

The scheduler creates, configures, and runs nodes:

auto sched = horus::Scheduler()
    .tick_rate(100_hz)      // 100 Hz main loop
    .prefer_rt()            // use RT scheduling if available
    .deterministic(true)    // reproducible execution
    .watchdog(5_s);         // 5 second watchdog

Nodes — Three Ways

1. Lambda (quick scripts):

sched.add("motor_ctrl")
    .rate(1000_hz)
    .budget(800_us)
    .on_miss(horus::Miss::Skip)
    .order(0)
    .tick([&] { /* control logic */ })
    .build();

2. Struct-based (stateful, lifecycle hooks — like Rust impl Node):

class Controller : public horus::Node {
public:
    Controller() : Node("controller") {
        scan_ = subscribe<msg::CmdVel>("lidar.scan");
        cmd_  = advertise<msg::CmdVel>("motor.cmd");
    }

    void tick() override {
        auto scan = scan_->recv();
        if (!scan) return;
        msg::CmdVel cmd{};
        cmd.linear = scan->get()->linear > 0.5f ? 0.3f : 0.0f;
        cmd_->send(cmd);
    }

    void init() override { /* called once */ }
    void enter_safe_state() override { /* stop motors */ }

private:
    Subscriber<msg::CmdVel>* scan_;
    Publisher<msg::CmdVel>*  cmd_;
};

Controller ctrl;
sched.add(ctrl).rate(100_hz).order(10).build();

3. LambdaNode (declarative — like Python horus.Node()):

auto node = horus::LambdaNode("controller")
    .sub<msg::CmdVel>("lidar.scan")
    .pub<msg::CmdVel>("motor.cmd")
    .on_tick([](horus::LambdaNode& self) {
        auto scan = self.recv<msg::CmdVel>("lidar.scan");
        if (!scan) return;
        self.send<msg::CmdVel>("motor.cmd", msg::CmdVel{0, 0.3f, 0.0f});
    });

sched.add(node).order(10).build();

Zero-Copy Publishing (Loan Pattern)

The loan pattern gives you a direct pointer to shared memory — no copies:

horus::Publisher<horus::msg::CmdVel> pub("cmd_vel");

auto sample = pub.loan();       // ~3ns — get SHM buffer
sample->linear = 0.5f;          // 0ns — direct write
sample->angular = 0.1f;         // 0ns — direct write
pub.publish(std::move(sample));  // ~3ns — make visible
// Total overhead: ~6ns constant, regardless of message size

Subscribing

horus::Subscriber<horus::msg::CmdVel> sub("lidar.scan");

auto scan = sub.recv();          // returns std::optional
if (!scan) return;               // no message yet
float range = scan->get()->linear;  // read from SHM

Duration Literals

using namespace horus::literals;
auto freq   = 100_hz;    // Frequency(100.0)
auto budget = 5_ms;      // 5000 microseconds
auto jitter = 200_us;    // 200 microseconds
auto timeout = 3_s;      // 3 seconds

Message Types

HORUS provides 70+ pre-defined message types in horus::msg:::

CategoryTypesHeader
GeometryTwist, Pose2D, Pose3D, Quaternion, Vector3, Point3msg/geometry.hpp
SensorLaserScan, Imu, Odometry, JointState, NavSatFixmsg/sensor.hpp
ControlCmdVel, MotorCommand, ServoCommand, PidConfigmsg/control.hpp
VisionCameraInfo, RegionOfInterest, StereoInfomsg/vision.hpp
NavigationNavGoal, Waypoint, GoalResultmsg/navigation.hpp
DetectionDetection, Detection3D, BoundingBox2Dmsg/detection.hpp
DiagnosticsHeartbeat, EmergencyStop, SafetyStatusmsg/diagnostics.hpp

All types are #[repr(C)] compatible — identical memory layout in Rust and C++ for zero-copy SHM transfer.

Single Include

#include <horus/horus.hpp>  // Everything: Scheduler, Topics, Messages, Literals

Building

horus build          # builds using horus.toml configuration
horus run            # builds and runs
horus test           # builds and runs tests via ctest

With CMake directly

cmake_minimum_required(VERSION 3.20)
project(my_robot LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)

find_package(horus REQUIRED)
add_executable(my_robot src/main.cpp)
target_link_libraries(my_robot PRIVATE horus::horus)

Performance

The C++ FFI boundary adds 15-17ns per call — comparable to a virtual function call. A scheduler tick with one C++ node completes in 250ns median. Throughput: 2.84 million ticks/second.

For full benchmark results with percentiles, scalability analysis, and ASAN validation, see the Benchmarks page.

What's Next