Sep 5, 2026 · rust · mixed-language · middleware · architecture
Should You Use One Language or Several in a Robot?
Stay in one language until a library forces your hand; most robots end up with two. ROS 2 suits mixed teams, HORUS suits a mixed stack on one machine.
Use one language until a specific part forces another; most robots end up with two, and ROS 2 or HORUS carries the boundary between them. One language means one build, one set of types and one place a bug can hide. The verdict flips when the model or driver you need exists only in a language that is not yours, which happens more often than Rust developers expect. The rest of this post is for a Rust developer deciding whether to keep the whole robot in Rust or let Python and C++ in.
You started in Rust on purpose. You wanted the compiler to catch the mistakes that otherwise show up as a machine doing something surprising at the wrong moment, and it does. Then you picked a depth camera, and the SDK is a pile of C++ headers with a build script that assumes a particular distribution. Then the detector you wanted to run ships as a Python package with weights, and the maintainers have no interest in anything else. So you wrote bindings, and now your evenings go to the binding layer rather than to the robot: matching struct layouts, chasing a crash that only happens on shutdown, discovering that the Python side holds a lock while your control thread waits. You have considered rewriting the driver in Rust and found several thousand lines of vendor quirks that exist because the hardware is strange. You have considered giving up and writing the whole thing in Python, and you know what that costs in the control loop. What you want to know is whether a one-language robot is a realistic goal or a beginner's wish, and if it is not, where the seam between languages should sit.
Should I write my whole robot in one language?
Write the whole robot in one language if the libraries you need exist there, and stop trying the moment they do not. One language removes an entire category of work: one build, one debugger that steps through everything, one place where a type is defined, one crash that shows you the whole stack rather than half of it. That is worth more than most people credit, and it is why a single-language robot is genuinely faster to move forward on while it lasts. What ends it is never taste. It is a specific dependency: an arm whose only SDK is C++, a camera whose driver is C++, a model whose only runnable form is a Python package. At that point your choice is to reimplement the dependency, to wrap it, or to run it in its own process and talk to it. Reimplementing is the option people underestimate the cost of, because vendor drivers are long for a reason, and that reason is usually undocumented hardware behaviour. The question is not whether you will mix languages. It is whether you will decide where the seam goes or discover it later.
What does a mixed-language robot look like in practice?
A mixed-language robot is a set of programs, each written in whatever suited its job, exchanging messages. In the common shape, a C++ driver talks to the camera because the vendor wrote it, a Python part runs the detector because that is where the model lives, and a Rust part runs the control loop because that is where a pause would hurt. None of them share a compiler. They agree on the shape of the messages and nothing else. There is a second shape, where one program hosts several languages inside it through bindings: Python calling into Rust, or Rust calling into a C++ library. That is one process, one crash boundary, one memory space, and it is a different set of trade-offs entirely. Most robots that grow past a demo end up with both shapes present: a couple of processes talking to each other, and inside one of them, a binding to a library nobody wants to rewrite. Knowing which shape you are in matters, because a fault in the first shape kills one part, and a fault in the second kills everything.
What are the actual ways to join two languages on a robot?
There are five, and they differ mainly in what happens when one side misbehaves. You can keep everything in one language and reimplement what is missing, which is clean and sometimes weeks of work. You can use bindings, so one process holds both languages and calls across directly, quick and tight and with no isolation at all. You can use ROS 2, which supplies client libraries for C++ and Python plus community bindings for Rust, and is the ordinary answer for a mixed-language robot, especially one spread over several computers. You can use a broker such as MQTT or ZeroMQ, which is how you join languages when the parts are on different machines and you want no framework. Or, when every part shares one machine, you can use a shared-memory middleware such as HORUS, open source under Apache-2.0 like ROS 2, where Rust, Python and C++ read the same ring buffers, so a message crossing between languages is not packed and unpacked on the way.
| Option | Who it is for | What it assumes you know | When to pick it | When not to |
|---|---|---|---|---|
| Everything in Rust | Solo builders who control the hardware | Rust, and patience for missing drivers | Every part you need exists in Rust | The vendor SDK is C++ only |
| Everything in Python | Fast iteration, research, teaching | Python and its packaging | Nothing you do is deadline-bound | The inner control loop must not pause |
| Bindings in one process | Teams wrapping one stubborn library | Memory layout across languages | The library is small and stable | You need one part to fail alone |
| ROS 2 | Mixed teams and multi-computer robots | Build tools, topics, message definitions | Parts are spread across machines | The whole robot is one program |
| Broker such as MQTT | Devices reporting to a server | Networking and brokers | Parts are far apart and loosely coupled | Parts share a control cycle |
| HORUS | Mixed stacks living on one machine | Rust, Python or C++, and processes | Handoffs must land inside a cycle | You need a full driver ecosystem |
Why does Python end up in almost every robot eventually?
Python arrives with the models and the tools, and almost nobody manages to keep it out. The detector, the pose estimator, the calibration script, the plotting notebook and the vendor's example code are all Python, and each one arrives separately, so no single decision was ever made to add it. The pattern is predictable: you plan a Rust robot, you need one model, the model has a Python API, you decide to shell out to a Python script "just for now", and a year later three parts of your robot are Python and one of them is inside a loop that should not pause. The mistake is not admitting Python. It is admitting Python without deciding where it is allowed to run. A useful rule: Python above the control loop, never inside it, because the interpreter's memory manager pauses at moments you did not choose and a control cycle cannot absorb a pause it did not plan for. Keep the part that must be on time in Rust or C++, let the part that must be quick to change be Python, and make the boundary between them explicit rather than accidental.
What goes wrong when the language boundary is in the wrong place?
The robot behaves correctly and intermittently, which is the worst combination to debug. The classic wrong placement puts the boundary inside the control path: the Rust controller asks the Python part for a number every cycle and waits for the answer, so the loop now runs at the pace of an interpreter that occasionally stops to collect memory. The arm is smooth, then it hesitates, then it is smooth again, and nothing in your control code changed. A second wrong placement splits one piece of state across the seam, so two languages each hold half of the robot's idea of where it is, and they disagree for a moment after every update. A third puts the boundary somewhere that copies large data often, such as passing whole camera frames back and forth for small answers. The tell for all three is that the fault correlates with load rather than with position or command. The fix is rarely a faster boundary. The fix is moving the boundary so the on-time work sits entirely on one side of it.
Which answer fits me if I am a Rust developer new to robotics?
Start in Rust for your own logic, and accept borrowed code in other languages for hardware and models. You will get more out of Rust in robotics than in most fields, because the bugs Rust prevents are exactly the ones that produce a machine doing something unrepeatable at the wrong instant, and because there is no collector waiting to pause your control loop. What you will not get is driver coverage, and this is the part newcomers underestimate: the ecosystem gap is not a matter of a few missing packages, it is the depth camera, the arm, the lidar and the simulator bridge, all written against C++ or Python. So plan for a robot whose control and state live in Rust and whose edges are other people's code. If your robot needs the wider ecosystem more than it needs your control loop to be exact, the honest answer may be to start in the framework everyone else uses and revisit later, a trade covered in do you actually need ROS 2 to build a robot.
Does the computer on the robot change whether I can mix languages?
Yes, in one specific way: the number of machines decides which joining methods are even available. If every part runs on one computer, shared memory is on the table, and that is the setting where mixing languages costs you the least, because the message a Python part wrote can be read by a Rust part where it already sits. If parts run on two computers, shared memory is out, and your choice narrows to a framework or a broker that speaks over the network, with all the encoding that implies. On a small board there is a second consideration: every language you add brings a runtime, and a Python runtime is not free in memory even when idle. Three languages on a board with modest memory is a real constraint rather than a stylistic one. And if part of your robot is a microcontroller, that part will be C or C++ regardless of what you prefer, so a mixed-language robot is often already decided by the hardware you chose before you thought about languages at all.
Does a demo next month change the language decision?
A demo next month means use whatever language your hardware's examples are written in, and do not fight it. Short deadlines are won by copying working code, and the working code for your camera, your arm and your model was written by someone with different tastes than yours. Take the Python example, get the machine moving, and let the architecture be embarrassing. For a project measured in a few months, it becomes worth putting the on-time work into one language and keeping the boundary explicit, because that is the change that gets harder as more parts appear. For a project that ships to someone else, the language question becomes a maintenance question: who will be able to change this in two years, how many toolchains must a new person install before the robot builds, and which of your dependencies is one unmaintained wrapper away from stranding you. That last one deserves an honest look while you still have the option of choosing differently.
What if only one person on the team knows the second language?
Then treat that language as a liability you are choosing on purpose, and keep its footprint small enough that one person can own it. A team of three with one Rust developer and two Python developers can absolutely ship a robot with a Rust control loop, provided the Rust part stays small and stable, is rarely touched, and has its interface written down. What kills that arrangement is a Rust part that keeps growing, until the Python developers cannot fix anything below the perception layer and the Rust developer becomes a queue. The same applies in reverse: one Python specialist and a Rust team ends with a detector nobody else can modify. Two practical defences. First, keep the shared vocabulary in the message definitions, so both sides can read what crosses the seam without reading the other side's code. Second, review the seam more carefully than anything else, since it is where misunderstandings between the two halves of your team turn into faults on the machine.
What do I give up by running two languages on a robot?
You give up the debugger that sees everything, and that loss is larger than it sounds. In one language you can follow a value from where a sensor produced it to where a motor consumed it in a single stepping session. Across a boundary you cannot: you have two processes with two debuggers, or one process with a stack that goes opaque halfway down, and the interesting bugs are exactly the ones that live at the seam. You also give up a single build, so a new person installs two toolchains and your continuous integration runs two pipelines. You give up one definition of a message type, because now the same structure is described twice and the two descriptions can drift, which produces a class of failure where nothing crashes and the numbers are subtly wrong. And you take on version skew between ecosystems that release on their own schedules. The gain is that each part is written in the language that suits it. That gain is real, and it is worth this bill, but the bill is not zero.
When is ROS 2 the better choice?
ROS 2 is the better choice for most mixed-language robots, and the reason is bluntly practical: ROS 2 is where the drivers and the message definitions already are. If your camera, your arm and your lidar all ship supported ROS 2 drivers, adopting anything else means writing the glue for each of them yourself, and that work is neither interesting nor short. If your robot spans more than one computer, ROS 2 was designed for exactly that and shared memory does not cross machines. If your team will grow, ROS 2 is the vocabulary new hires already speak, and Python and C++ parts talking over ROS 2 topics is the most ordinary arrangement in robotics. HORUS is not the answer in those cases, because a shared-memory layer for one machine solves the narrow problem of local handoffs rather than the broad problem of a framework and its ecosystem. The honest split: use ROS 2 unless every part of your robot lives on one computer and the handoff between the languages is where your deadline is being lost.
Is writing the whole robot in Rust the obvious answer?
No, and here is why: the language is the smaller half of the decision, and the ecosystem is the larger half. Rust genuinely fits robot control, and none of what follows argues otherwise. But a robot is mostly integration, and integration means using other people's code for hardware you did not design. Rust's robotics library coverage is improving and is still thin next to C++, which has had drivers written for it for two decades, and Python, which is where every model lands first. So the all-Rust plan usually runs into one of two walls: you reimplement a vendor driver and spend weeks discovering the undocumented behaviour that made the original long, or you wrap it and now you have C++ in your process anyway, with a boundary you have to maintain. Neither is a disaster, and both are work you did not plan for. The version of this that succeeds is narrower: Rust owns the state and the control loop, other languages own the edges, and you stop feeling bad about it.
Does every message between two languages have to be translated?
Partly, but not the way you think. Crossing a language boundary does not require translation by nature; it requires that both sides agree on how the data is laid out. What forces packing and unpacking is the transport in between. A network transport must turn the message into a stream of bytes because that is what a network carries, and that step is unavoidable when your parts are on different machines. Between processes on one machine it is avoidable, because a shared-memory middleware lets both sides read the same bytes where they already sit, so a Python reader and a Rust writer meet in the same buffer rather than through a copy. This is why the one-machine case deserves its own consideration rather than being lumped in with the network case. What you still pay, in every arrangement, is agreement: both languages must describe the message the same way, and keeping those descriptions in step is the ongoing maintenance cost of a mixed-language robot.
How do I decide where to put the language boundary?
Draw the boundary around the work that must be on time, and put everything else outside it. Start by writing down which part of your robot has a deadline that produces a physical consequence when missed: the arm stops before it hits the table, the wheels correct before the robot leaves the line. That part, and the state it reads, goes in one language with no pauses you did not choose, which in practice means Rust or C++. Everything that thinks, plans, logs, calibrates or shows you a picture goes outside, and can be whatever is convenient. Then check the seam for two smells: does the on-time part ever wait on the other side, and does any one piece of state live on both sides at once. Both are signs the line is in the wrong place. If you cannot yet name the deadline, you are not ready to draw the boundary, and a single language is the right default until you can. The layers underneath this decision are covered in what runs on a robot besides your code, and the cost of ceremony at the seam in a robot node in eight lines of Python.
Where that leaves you, roughly in order of how often it applies:
- If every library you need exists in Rust -> one language, because a single build and one debugger beat every other advantage.
- If a vendor SDK or a model blocks you -> two languages with an explicit seam, because reimplementing a driver costs more than it looks.
- If your parts sit on several computers -> ROS 2, because the network transport and the drivers arrive together.
- If every part is on one machine and the handoff is late -> a shared-memory middleware, because the copy between languages is what you are removing.
- If one person owns the second language -> keep that part small and written down, because a queue of one is how projects stall.
When you compare the options, the HORUS Fit Framework keeps the comparison honest along five axes with no numbers in them: ecosystem size, setup effort, team size fit, deployment target, and licence. A choice that wins four and loses the one your robot depends on is still wrong.
If your robot is one machine running Rust, Python and C++, put HORUS on the shortlist while the seam is still cheap to move: star it so it is in your list when you start building, rather than half-remembered on the day the loop is already hesitating.