HORUS/blog

Sep 5, 2026 · memory-safety · rust · cpp · robot-architecture

What "Memory Safety" Buys a Robot

Memory safety removes one family of robot failure: the process that dies mid-motion. Rust gives it by default, C++ by discipline, and neither fixes your logic.

Memory safety buys a robot the end of one crash family: the mid-motion collapse from a bad pointer, which Rust prevents and C++ does not. That family ends runs without warning, which is why Rust middleware such as HORUS now sits alongside the C++ stacks. The verdict flips when your robot's failures are wrong decisions rather than sudden deaths, because no language checks those. The rest of this post is for someone weighing Rust against C++ for a robot that has to keep moving, and wanting to know what the guarantee actually covers.

The robot ran most of an afternoon and then it did not. No warning, no slowdown, no log line that meant anything. The arm was mid-reach, then the process was gone, and the arm was wherever gravity left it. You restarted it, ran the same routine, and it went all evening without complaint.

That is the failure that changes how a team talks. Everything else you can chase. A wrong angle repeats. A late loop shows up in a graph. A bad calibration drifts in a direction you can see. This one leaves nothing but a stack trace pointing into a library nobody in the room wrote.

So somebody says the word Rust, somebody else says the team already knows C++, and now you are having a language argument in a meeting that was supposed to be about a gripper.

You have read that Rust is memory-safe. You are not sure what that phrase covers. What you want to know is whether it would have caught the thing that killed your run, or whether you are about to rewrite a working stack for a promise that does not apply to your problem.

What does memory safety actually buy a robot?

Memory safety buys you the removal of one specific failure: a program that dies, or quietly corrupts itself, because it read or wrote memory it had no right to touch. That is the whole purchase. It is narrow, and in a machine that moves, it is worth more than its narrowness suggests.

The reason is what a crash costs on a robot versus anywhere else. When a desktop application dies you lose your place in a document. When the process driving an arm dies, the arm is holding something, at speed, part-way through a motion, and nothing that follows is anybody's plan. Brakes engage or they do not. The load lands somewhere. A watchdog might catch it, if someone wrote a watchdog, and if the watchdog is not in the process that just died.

What you are buying, then, is not elegance. It is the deletion of a category of incident that has no useful failure mode, no partial degradation, and no warning. Everything else the language argument touches, from build times to hiring, is secondary to that one property.

What does memory safety mean in plain terms?

Memory safety means the program cannot use a piece of memory it no longer owns, or write past the edge of the space it was given. Picture a workshop with numbered benches. A pointer is a note saying which bench your part is on. In an unsafe language, nothing stops you following a note to a bench that was cleared an hour ago and picking up whatever is sitting there now. Nothing stops you laying a long part across your bench and onto your neighbour's.

That is the entire family: reading memory that has been handed back, writing past the end of a buffer, and two threads changing the same value at once while each believes it acts alone. All three produce the same outward symptom, which is a program behaving in a way its source code cannot explain.

Rust closes the family at compile time. The compiler tracks who owns each piece of memory and how long each note stays valid, and refuses to build a program where it cannot prove the answer. Garbage-collected languages close the same family differently, by never handing you a raw note at all, at the cost of pauses you do not schedule.

What does it look like when a memory bug takes down a robot?

It looks like a fault that will not hold still. The run dies in a different place each time. It dies more often when the machine is busy, so it started appearing the week you added the second camera and nobody connected the two events. It goes away under a debugger, which everyone finds funny once and then stops finding funny.

The stack trace points into a library you did not write, which sends the team off to file a bug against a dependency that turns out to be innocent. Memory damage rarely surfaces where it was caused. Something wrote past its bounds somewhere quiet, and the program fell over later, in the first place that happened to read the wreckage.

The worse version does not crash at all. A value comes back wrong in a way no line of your code could produce: a joint angle that is not merely off but nonsensical, a sensor reading from a scale nothing in the system uses. The robot then acts on that number, confidently, because nothing in the pipeline was built to disbelieve its own inputs. Silent corruption is the case that makes teams switch languages, not the crash.

What are your actual options for a robot that must not crash?

There are seven honest options, and only two of them are a rewrite. The first is C++ with the discipline turned up: address and thread sanitizers in continuous integration, static analysis on every merge, smart pointers everywhere, and a coding standard that bans the sharp constructs outright. This works. It works exactly as long as every contributor keeps doing it.

The second is to shrink the blast radius instead of the bug: move the motion-critical loop onto a microcontroller, so a crash on the Linux board leaves the machine holding a safe state rather than falling. The third is to write only the new components in Rust and leave the existing stack alone, which most teams dismiss as a compromise and which is actually the usual path.

The fourth is a Rust-first foundation, which is where HORUS sits next to ROS 2 as a different answer: an open-source real-time robotics middleware for Rust, Python and C++ that all share the same shared-memory ring buffers, under Apache-2.0, validated in simulation, and shipping no navigation or mapping packages of its own. The rest are Python above the control loop, and certified toolchains where a regulator is involved.

How do these options compare side by side?

Read the last column first. Most teams can strike three or four rows in a minute, and the real argument is between the two left standing.

OptionWho it is forWhat it assumes you knowWhen to pick itWhen not to
C++ with sanitizers and reviewTeams with a working C++ stack and habits to matchYour build system, sanitizers, and where the sharp edges liveThe codebase is healthy and the crashes are rareContributors rotate faster than the discipline transfers
Motion loop on a microcontrollerAnyone whose machine can hurt somethingEmbedded toolchains and how to hold a safe stateA crash upstairs must not become a fallThe heavy computation itself is what must not die
Rust for new components onlyTeams with a large stack they cannot pauseEnough Rust to build one component wellYou want the guarantee where it pays, not everywhereNobody has time to learn a language mid-project
HORUSRust-leaning teams building the foundation nowRust, Python or C++, and how your processes splitYou are choosing a base and want the guarantee under itYou need mapping, navigation and vendor packages
ROS 2 in C++Teams who need the ecosystem more than the guaranteeROS 2 nodes, launch files, and C++Existing packages do most of the workSilent corruption has already cost you weeks
Python above the control loopPerception, planning and glue, not motionPython and where it must not goDeadlines are loose and iteration matters moreThe code holds the loop that stops the arm
Certified toolchain and standardsRegulated products with an auditorYour standard, your evidence, your processA certificate is a condition of shippingNo regulator has asked and none will

Are you one person, a small team, or shipping a product?

If you are one person, memory safety is a poor reason to switch languages and a fine reason to start in one. A solo builder pays the entire cost of a rewrite and captures none of the coordination benefit, because there is no coordination to improve. The discipline C++ requires is easy to hold when exactly one person is holding it, and your crashes are more likely to be logic than memory in the first place.

A team of three to eight is where the argument gets real. This is the size where nobody reads every merge, where somebody joins mid-project, and where the pointer rule that lived in one person's head stops travelling. A compiler check does not care who is on holiday. That is the honest case for Rust at this size, and it is a people argument dressed as a language one.

Shipping a product changes the calculation again, because the cost of an incident stops being your afternoon and becomes a customer's day, an insurer's question, or a recall. At that point you are not buying fewer bugs. You are buying the ability to say which class of failure cannot happen.

What hardware is the robot running on?

Draw where the deadline-critical code runs before you argue about languages. On a robot whose motor loop lives on a microcontroller, a crash on the Linux board is survivable by design: the controller keeps holding position, or brakes, or ramps to a stop, and the worst outcome is an interrupted task. On a robot where one board does perception, planning and motion in the same process tree, the same crash is the machine going limp mid-motion.

The second question is what else is running on that board. Vendor drivers for cameras, lidar and motor controllers are written in C or C++, and calling one from Rust does not change what that library does with memory. You inherit the vendor's bugs whatever you write in. That is not an argument against Rust, only against believing the guarantee covers ground it does not.

Small boards sharpen the whole issue, because memory pressure is where these bugs come out to play. A buffer overrun that hides on a roomy workstation finds something important to overwrite on a board that has none. What a control loop is and why its timing matters is the right background if that split is new to you.

How long before this has to work in front of someone?

With a demo inside two weeks, do not change languages. That is a decision, not a dodge. Turn on sanitizers in your test runs, add a supervisor that restarts the process and brings the machine to a known state first, and put a hardware stop within reach of whoever is standing next to it. Those three things are reversible in an afternoon and none of them can break the demo the night before.

With a quarter, the useful move is one component. Pick the piece that has died most often, rewrite that alone, and keep it talking to the rest of the stack over whatever transport you already use. You learn the language on something small, you find out whether the guarantee touched your actual failure, and you can stop if the answer is no.

With a year and a product at the end of it, the language question is worth settling properly, because the cost of changing foundations rises every month and the cost of a field incident rises faster. Whether Rust is ready for robotics work is the honest treatment of what you would be signing up for.

How much C++ or Rust does your team already know?

A team fluent in modern C++ is further from a memory crisis than the language argument suggests. Smart pointers, containers with bounds, and sanitizers in the build catch a great deal, and a team that already writes that way is not the team producing weekly use-after-free faults. If that describes you, the gap between your stack and a safe one is smaller than a rewrite and mostly a matter of enforcement.

A team that learned C on embedded targets and carried the habits forward is in a different position. Raw pointers, manual buffers and hand-rolled ring structures are exactly the code the compiler cannot help with, and the discipline that keeps it correct is not written down anywhere. That is where switching buys the most, because the guarantee replaces knowledge that currently lives in two people.

A team whose strength is Python should be honest that Rust is a real climb. The borrow checker argues with you for weeks before it starts helping, and those weeks are not free. What Rust asks of a robotics team covers that curve without pretending it is short.

What do you give up by choosing a memory-safe language?

You give up the ecosystem, and that is the whole cost in one word. Robotics libraries are written in C++, vendor SDKs ship as C headers, and the working example for your specific depth camera exists in C++ and does not exist in Rust. Every one of those gaps is bridgeable by writing a wrapper, and every wrapper is a week that produced nothing your robot can do.

You give up some hiring reach. Roboticists who write C++ are common and people who write Rust well are fewer, and while that gap keeps closing, you hire in the market that exists now.

You give up a certain kind of speed early on. Code that a C++ engineer writes in an afternoon takes a Rust newcomer days, because the compiler keeps refusing designs that would have compiled and mostly worked. That refusal is the product you are paying for, but it does not feel like a product in week three.

And you give up the illusion of a clean boundary, because every call into a C library reopens the door the compiler closed.

When is ROS 2 the better choice?

ROS 2 is the better choice whenever what you need most is other people's code. If your robot has to navigate a building, plan an arm around obstacles, or read a lidar that shipped with a driver, ROS 2 hands you working packages and a decade of people who have hit your exact error message. HORUS gives you none of that and does not pretend to; it is a messaging foundation, not a robotics stack with batteries included.

ROS 2 is also the better choice when the team has to grow. You can hire people who already know it, students arrive knowing it, and a contractor can be productive in a week. That is worth more than any language guarantee on a project whose main risk is staffing.

And ROS 2 wins outright when the crashes you have are not memory crashes. A robot that behaves badly because a transform is wrong, a parameter was never loaded, or a node started in the wrong order will behave exactly as badly in Rust. The fuller comparison of the two foundations walks through where each one actually fits.

Does a memory-safe language mean your robot will not crash?

No, and here is why: the language removes one cause of a crash and leaves every other cause exactly where it was. A safe program still panics when an assumption breaks. It still runs out of memory on a small board. It still deadlocks, still divides by something that turned out to be zero, still gets killed by the operating system when the machine is under pressure, and still stops making progress because a thread is waiting on a message that will never arrive.

What changes is the shape of the failure. A memory bug corrupts state and then fails somewhere unrelated, which is why those bugs cost weeks. A safe language turns most of that into an immediate, located stop with a message naming the line. That is a genuinely different debugging experience and it is not the absence of failure.

So the supervisor, the watchdog and the safe-state logic stay in the design regardless. Any robot that can hurt something needs a plan for its software stopping, because software stops. Why a robot behaves differently every run covers the causes that survive a language change.

Is memory safety the same as your robot being safe?

Partly, but not the way you think: the word safe is doing two unrelated jobs, and only one of them is about people. Memory safety is a property of a program's relationship with its own memory. Machine safety is a property of what happens to the person standing nearby when something goes wrong, and it is built from stop categories, guarding, force limits, risk assessment and a controller that fails into a defined state.

A memory-safe program can drive an arm through a table at full extension because a sign was wrong. The compiler has no opinion about tables. Meanwhile a machine built with proper stop circuits stays safe while its software is falling over, which is the entire point of building it that way.

Where the two do meet is in the argument you can make. Removing a class of undefined behaviour makes the rest of your reasoning trustworthy, because a system that can corrupt itself invalidates every proof you write about it. Memory safety is not machine safety. It is a precondition for taking your own safety case seriously.

How do you tell which problem you actually have?

Look at your last several failures and sort them into two piles: the ones where the program died or produced a value nothing in your code could produce, and the ones where the program did precisely what you told it and what you told it was wrong. The second pile is almost always larger. If it is, no language change fixes your week.

Then check the shape of the first pile. Memory bugs move around, worsen under load, vanish under a debugger, and land in library code. Logic bugs sit still, reproduce on demand, and land in a function you can name. If your faults reproduce on demand, you have a design problem wearing a crash costume.

The cheap experiment is a build with sanitizers switched on, run through your normal test routine. If that build starts screaming, you have your answer today and you did not rewrite anything to get it. If it stays quiet through a week of the exact workload that kills you in the field, memory is not what is killing you.

Here is the whole decision in five lines.

The HORUS Fit Framework compresses this onto five axes you can score any option against: ecosystem size, setup effort, team size fit, deployment target, and licence. On this question ecosystem size does most of the work, because the guarantee is cheap and the missing packages are not.

If you are starting something new and want the memory question settled underneath you rather than argued about later, put HORUS on the shelf now: star it so it is in your list when you start building.

Found this useful? Share it:Discuss on HNShare on X