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:::
| Category | Types | Header |
|---|---|---|
| Geometry | Twist, Pose2D, Pose3D, Quaternion, Vector3, Point3 | msg/geometry.hpp |
| Sensor | LaserScan, Imu, Odometry, JointState, NavSatFix | msg/sensor.hpp |
| Control | CmdVel, MotorCommand, ServoCommand, PidConfig | msg/control.hpp |
| Vision | CameraInfo, RegionOfInterest, StereoInfo | msg/vision.hpp |
| Navigation | NavGoal, Waypoint, GoalResult | msg/navigation.hpp |
| Detection | Detection, Detection3D, BoundingBox2D | msg/detection.hpp |
| Diagnostics | Heartbeat, EmergencyStop, SafetyStatus | msg/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
With HORUS CLI (recommended)
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
- Obstacle Avoidance Tutorial — build a complete reactive controller
- Multi-Node Pipelines — sensor → processor → actuator patterns
- C++ vs Rust vs Python — when to use each language
- API Reference — complete class documentation