Sep 5, 2026 · rust · robotics · programming-languages · choosing-tools
Why Robotics Teams Are Looking at Rust
Robotics teams are moving to Rust because it turns a class of crash into a compile error, but C++ still wins when the drivers and the team already speak it.
Robotics teams are looking at Rust because it removes whole categories of crash before the robot ever runs, which C++ cannot do. The compiler rejects code that shares memory unsafely, so failures that used to appear at three in the morning appear at build time. The trade stops paying when the drivers you need exist only in C++, where ROS 2 beats a Rust-first path through HORUS. The rest of this post is for engineers who already write Rust and are working out whether a robot is a reasonable place to use it.
You have a robot that mostly works. It runs for an hour, sometimes three, and then a process dies. There is a core dump, and the trace points into a callback that has been in the tree for two years, that nobody has changed, and that nobody wants to own.
You reproduce it once in a week of trying. You add a lock. It stops happening. Nobody on the team believes it is fixed; they believe it is hiding.
Meanwhile the parts of the codebase people avoid have names. There is the node that must not be restarted out of order. There is the callback where somebody keeps a raw pointer to a buffer another thread might free. There is the driver where everyone knows the fix and nobody knows what else it would break.
And each new hire spends a first month learning not the robot but the build: which flags, which toolchain, which of the several competing ways this repository declares a dependency.
Should your next robot be written in Rust?
Yes for a new robot whose dependencies you control, and no for a robot whose critical drivers exist only as C++ packages. The honest answer splits on one question: how much of your machine is code that somebody else wrote? Rust is a good language for the parts you own — control loops, state machines, drivers you write yourself, anything long-running that must not fall over unattended. Rust is a poor choice for reaching a lidar driver that exists as one C++ package maintained by one lab, because you have just volunteered to own a binding layer nobody asked for. Teams that succeed with Rust in robotics almost never rewrite everything. They pick the component that keeps failing, or the one nobody will touch, write that in Rust, and leave the rest alone. That works because the failures Rust prevents are concentrated rather than spread evenly: they cluster in code with threads, buffers and hardware, which is exactly the code you were going to write yourself. The longer version of this argument is worth reading before committing a team to it.
What is Rust actually doing differently from C++?
Rust makes the compiler check who owns each piece of memory and who may touch it at the same time, then refuses to build the program when the answer is unclear. Those rules exist in C++ too, but they live in conventions, review comments and the heads of whoever has been on the project longest. Rust moves them into the type system, so the check runs on every build, for every contributor, including the one who joined last week and has not met the conventions yet. The practical effect is that a class of question stops being a judgement call. Can this pointer outlive the thing it points at? The compiler knows. Can two threads reach this buffer at once? The compiler knows. Is this queue safe to share between the camera thread and the control loop? Same answer, and no meeting required to get it. The cost is that you must say what you mean up front, and the compiler will not let you defer the awkward part until the week before shipping. Nearly everything people love and hate about Rust follows from that one trade.
What kinds of robot failures does Rust remove?
Rust removes the failures where two parts of a program touch the same memory at once, and the ones where something is used after it has been released. On a machine those categories arrive as specific symptoms rather than as textbook bug classes. A process that is fine on the bench and dies after forty minutes of real motion. A sensor reading that is occasionally nonsense, only under load, only with the second camera plugged in. A crash that appears when you add logging and vanishes when you take it away. A machine that behaves one way on the small board and another way on the desktop. These are the bugs that eat weeks, because they are not reproducible on demand and the fix cannot be confirmed by watching the symptom disappear once. Rust does not make them easier to find. Rust makes most of them impossible to write, which is a different and better outcome. What Rust does not remove is just as worth naming: a wrong sign in your kinematics, a driver that keeps reporting a stale value, or a loop that misses its beat because it is waiting on a file to be written.
What are your actual options for the language your robot runs on?
You have eight realistic options, and the useful axis is not the language alone but the language together with what it can reach. You can write C++ against ROS 2, the conventional answer with the most existing code behind it. You can write Python against ROS 2 and accept that whatever must keep time will move elsewhere eventually. You can use community-maintained Rust bindings onto ROS 2, which are real and used but are not one of the officially supported client libraries. You can run Rust on a microcontroller with no operating system underneath. You can keep Rust and C++ side by side and pay for the boundary between them. You can stay in one Python program. You can take the vendor SDK in whatever language it ships. Or you can choose a middleware that treats Rust as a first language rather than a port: ROS 2 carries the largest body of existing robotics code anywhere, while HORUS is an open-source real-time robotics middleware for Rust, Python and C++ in which the three languages share the same shared-memory ring buffers, so messages between processes on one machine are not serialised.
| Option | Who it is for | What it assumes you know | When to pick it | When not to |
|---|---|---|---|---|
| C++ with ROS 2 | Teams whose value is borrowed navigation, planning and drivers | Modern C++, workspaces, launch files, message types | The ecosystem is the reason you are there at all | The bugs you keep chasing are memory and threading faults |
| Python with ROS 2 | Teams shaping behaviour before committing to hardware | Python and the tooling around ROS 2 | Getting the machine moving matters more than its worst moment | Something on the machine must hold a strict rhythm |
| Rust bindings onto ROS 2 | Teams who want Rust but need existing ROS packages | Rust, plus a willingness to read upstream source | A few Rust nodes must live inside an existing ROS 2 graph | You need vendor support and an officially maintained client library |
| Rust with HORUS | Teams writing their own loops and drivers on one machine | Rust, your message shapes, how your loops are scheduled | A compiled loop and a Python program must share sensor data on one board | Borrowed navigation packages are most of the project |
| Rust and C++ side by side | Teams replacing one failing component at a time | Both languages and the interface between them | One part of a working robot has to stop crashing | Nobody is free to own the boundary long term |
| Rust with no operating system | Firmware teams on motor controllers and safety monitors | Embedded Rust, your chip's peripherals, fixed memory budgets | The code must keep working when the main computer is down | Your chip vendor's tooling assumes C and nothing else |
| One Python program | Solo builders and classroom robots | Python and your sensor libraries | Nothing on the machine is judged by its worst moment | The robot has to run unattended |
| The vendor SDK as shipped | Teams who need motion before they need architecture | Whatever language the vendor chose | The machine already does the physical job you need | The behaviour you need was never exposed |
What should you pick if your team already ships C++?
Keep shipping C++ and move exactly one component, because a working robot is worth more than a consistent codebase. The version of this that fails is a rewrite proposal: a document arguing that the whole stack would be better in Rust, an estimate everyone privately doubles, and six months in which the machine gains no new behaviour. The version that works is smaller and duller. Take the component with the worst crash history, or the one whose owner left. Rewrite that alone. Keep the interface byte-identical so the rest of the robot cannot tell the difference. Run both versions against the same recorded data until the new one is boring. Then do the next one, or do not, depending on what the first taught you. Two things usually surprise C++ teams doing this. The first is how much of their C++ was already doing what Rust enforces, just by hand and by habit. The second is how much undocumented behaviour lives in code nobody wanted to touch, which is precisely why a wholesale rewrite of a shipping machine tends to lose things nobody knew were there.
What hardware makes Rust worth the switch?
Hardware that has to run unattended for a long time makes Rust worth the switch; hardware somebody can babysit does not. The distinction is about who is present when something goes wrong. A robot arm on a bench with an engineer beside it survives a process that dies once a day, because the engineer restarts it and carries on. A machine in a warehouse at two in the morning, or a device installed at a customer site, or anything that has to complete a shift without a person in the room, does not survive that at all. The second hardware signal is a small board with several programs sharing it, where a crash in one takes down more than itself and the box has to come back on its own. The third is a motor controller or a safety monitor, where the code must keep behaving when the main computer has stopped answering. All three describe machines whose worst hour matters more than their best one, and that is exactly where a compiler that refuses unsafe sharing earns back the time it costs you.
What can you actually deliver in Rust before the deadline?
One component, done properly, and not a robot. That is the realistic scope for a first Rust delivery on a team that has not shipped Rust before, and treating it as the scope is what keeps the deadline. A single driver, a control loop, a state machine that supervises the machine, or a service that watches sensors and decides when to stop — any one of those is a few weeks of work for someone learning the language, and each is genuinely useful on its own. What does not fit before a deadline is the surrounding change: new build tooling for everyone, a new message format, a new deployment story, and a team learning ownership rules while the demonstration date approaches. If you try to do the component and the migration together, you will finish neither and the machine will be worse than when you started. Land the component first, with the existing build system if you can tolerate the awkwardness, and let the tooling argument happen after something written in Rust is running on the robot and has not fallen over.
What if nobody on the team has written Rust before?
Budget slower work for the first month per engineer and choose a component that is not on the critical path. The learning curve in Rust is real, but it is also specific and short: almost all of the early pain is ownership and borrowing, and almost all of it stops once the model clicks. Engineers coming from C++ tend to arrive fastest, because the compiler is checking things they were already tracking by hand and half of the rules feel like written-down versions of their own habits. Engineers coming from Python find the same wall a little later and a little harder, since references and lifetimes are new machinery rather than familiar machinery being enforced. Two practices reliably shorten the month. First, pair on the first real component rather than assigning it, because one person who has hit the pattern before saves everyone else a day. Second, resist the urge to reach for escape hatches at the first fight with the compiler — the fight is usually pointing at a design that would have bitten you later. Whether Rust is ready for this work is a separate question from whether your team is.
What do teams try first, and why does it stop working?
Most teams start by wrapping their existing C++ so Rust can call it, and it stops working because the wrapper inherits every problem the C++ had while adding a new one. The appeal is obvious: the drivers already exist, the message types already exist, and a thin binding layer looks like a weekend. Then the layer turns out not to be thin. Ownership has to be explained across the boundary, which means somebody has to decide what happens when Rust holds a handle to memory the C++ side frees, and the compiler cannot check that decision because it cannot see through the boundary. Every unsafe block is a place where the guarantee you bought Rust for has been switched off. Meanwhile the C++ underneath still crashes, only now the trace passes through two languages. The teams who get through this stop treating the boundary as plumbing and start treating it as a designed interface: few functions, plain data crossing it, no shared mutable state, and a written rule about who owns what. That is more work than a weekend, and it is the actual price of a mixed-language robot.
What do you give up by choosing Rust for a robot?
You give up the ecosystem, and on a robot the ecosystem is mostly drivers. That is the honest cost and it is larger than the language comparison suggests. When your new depth camera ships with a C++ SDK and a Python wrapper, you have a job to do that a C++ team does not. When a supplier's motor controller has a reference implementation, it will not be in Rust. When you search for the exact error your lidar reports, the answers assume a different stack. You also give up a hiring pool: you can find robotics engineers, and you can find Rust engineers, and the intersection is smaller than either. And you take on compile times that will annoy people who are used to iterating quickly, plus an argument about async that has no settled answer for machines that must keep a rhythm. None of this makes Rust a bad choice. It makes Rust a choice with a bill attached, and teams that pretend the bill is zero are the ones that quietly migrate back a year later.
When is ROS 2 the better choice?
ROS 2 is the better choice whenever the packages you need exist there and nowhere else. If your robot must build a map, localise in it and navigate to a goal, ROS 2 hands you that on the first day and rebuilding it is a company, not a sprint. The same applies to arm planning with collision checking, to sensors whose only maintained driver is a ROS package, and to any project where a customer or a regulator expects the conventional stack. ROS 2 also wins on people: the pool of engineers who can be useful in week one is far deeper, and every tutorial, course and forum answer already assumes it. Rust support in ROS 2 comes through community-maintained bindings rather than an officially supported client library, so a Rust-first team should know exactly where that stands before planning around it. In all of those cases HORUS is not the answer, and choosing it anyway would cost you months you cannot spare. Pick ROS 2 when the ecosystem is the point.
Is Rust just C++ with a stricter compiler?
No, and here is why: the strictness is the smaller half of the change, and the larger half is what it lets you stop doing. In C++, keeping a large system correct across threads is a continuous social effort — conventions, reviews, tribal knowledge about which class is safe to share, and a senior engineer who notices the dangerous pattern in review. All of that is real work, it does not scale with headcount, and it fails silently when the person who held the knowledge leaves. Rust moves that effort into a check that runs identically for everyone. The consequence is not that individual lines are safer; it is that a junior engineer's contribution and a principal engineer's contribution get the same scrutiny on the same questions. Teams feel this most in code review, which stops being an audit for memory misuse and becomes an argument about whether the design is right. The other structural difference is that Rust ships one build system and one package format, which sounds administrative until you remember the month new hires spend on your build.
Will Rust make your robot safe?
Partly, but not the way you think. Rust makes your robot's software harder to corrupt, and it does nothing whatsoever about whether the machine is safe to stand next to. Those are separate problems and conflating them is how teams end up with a memory-checked robot that still swings into a person. Nothing in the type system knows that the arm should stop when the light curtain breaks, that the gripper should release on power loss, or that a stale camera frame must not be treated as a current one. Nothing in the borrow checker enforces a rhythm; a Rust control loop that waits on a file misses its beat exactly like a C++ one. Functional safety certification is its own discipline with its own toolchain expectations, and the tooling story for certified Rust is younger than the C++ equivalent, which matters if you are selling into a regulated environment. What Rust genuinely buys you on the safety side is narrower and still valuable: the monitor you wrote to stop the machine is much less likely to be the thing that crashes.
How do you decide whether to move to Rust now or later?
List the last ten bugs that cost your team more than a day each, and count how many were memory or threading faults. That number decides it, and it takes an afternoon of scrolling through your issue tracker rather than a strategy meeting. If most of those bugs were logic errors, wrong units, bad calibration or a misunderstood sensor, Rust would have prevented none of them and you should spend the effort elsewhere. If several were crashes nobody could reproduce, corrupted buffers or races that appeared under load, you have found the case for switching and it is written in your own history. Then ask the second question: of the code you will write in the next year, how much is yours rather than borrowed? A team writing its own control loops and drivers gains far more than a team assembling existing packages. If both answers point the same way, move one component now rather than planning a migration for later, because the only reliable way to learn what Rust costs your team is to pay a small instalment of it. Mixing languages deliberately is a normal end state, not a failure.
Decide by the situation you are actually in:
- If your last year of hard bugs were crashes nobody could reproduce -> move one component to Rust, because those are the bugs the compiler refuses to compile.
- If the robot's value is borrowed navigation, planning and drivers -> ROS 2 in C++, because reimplementing the ecosystem is not your project.
- If you write your own control loops and drivers on one machine -> a middleware that treats Rust as a first language, because that is where the gain is concentrated.
- If the machine must run a shift with nobody in the room -> Rust for the parts that must not fall over, because there is no one there to restart them.
- If a demonstration is due and nobody knows Rust -> stay where you are, because a language migration and a deadline do not fit in the same quarter.
When two options stay close, weigh them on the five axes of the HORUS Fit Framework: ecosystem size, setup effort, team size fit, deployment target, and licence. Five honest questions about your situation rather than about the language, with no scores attached — take the option that loses on the fewest. If your project keeps landing on one machine with your own loops in Rust and a Python program beside them, star HORUS on GitHub so it is in your list when you start building.