HORUS/blog

Sep 5, 2026 · multi-language · robot-software · team-decisions · python

Should You Mix Languages in a Robot Project?

Mix languages across process boundaries and not inside one program, because the real cost of a second language lands on the handoff and on your team.

Mix languages, but only at boundaries you can name, because a single-language robot forces either Python into the control loop or C++ into everything else. Mixing pays when each language does what it is best at and the handoff between processes is cheap, which is what ROS 2 and a middleware such as HORUS are for. It stops paying the moment your team is too small to keep two toolchains alive. The rest of this post is for a team deciding whether to add a second language to a robot that already works, or take one away.

The argument usually starts as a joke. Somebody says the perception code is Python because of course it is, somebody else says the controller is C++ because of course it is, everybody laughs, and a week later the piece joining the two is what broke the demo.

The symptoms after that are recognisable. A new engineer cannot run the robot from a clean checkout, because two build systems have to agree and one person knows the order. There is a file whose only job is to reshape data from one language into the shape the other expects, and four people have edited it. The Python process behaves until the machine gets busy, at which point the arm moves a beat late and nobody can say exactly why. Somebody has started rewriting a working node in Rust, on evenings, on a branch nobody has reviewed.

Underneath sits a question the team keeps not asking out loud. Are two languages the reason this is hard, or is the join between them the reason, or is it neither and we are blaming the tools for a design nobody drew?

Should you mix languages in a robot project?

Yes, mix them, but put every language boundary on a process boundary and never in the middle of a program that has to meet a deadline. That single rule removes most of what teams blame on multi-language robots, because the trouble rarely comes from having two languages. It comes from two languages sharing one program, one call stack and one set of assumptions about who is allowed to pause.

The reason to mix at all is that robots contain two kinds of work with opposite requirements. Perception, task logic and anything a human edits daily want a language that is quick to change and easy to read. Motor commands and safety checks want a language that does not stop to tidy up at an inconvenient moment. No single language is the best answer to both questions, and pretending otherwise is how teams end up writing C++ for a job that changes weekly.

The reason to hesitate is people rather than code. Two languages means two toolchains, two packaging stories and two pools of reviewers. If that whole cost lands on one person, the mix fails however well it was designed.

What does mixing languages actually mean on a robot?

Mixing languages means two runtimes owning two parts of the robot's job, and the phrase covers two arrangements that behave nothing like each other. The first is one program that loads a library written in another language and calls into it directly. The second is two programs running side by side, exchanging messages, each with its own memory and its own crash.

The first arrangement is the one that hurts. When a Python program calls into C++ and that C++ code calls back into Python, both languages are now bound to the same lifetime. A pause in one is a pause in the other. A crash in one takes both. Debugging means holding two mental models at once, and the stack trace you get usually explains only half of what happened.

The second arrangement is the one that works. Two processes are two separate things that agree on a message shape. Either can be restarted, profiled, replaced or rewritten without the other noticing, which is the whole point. The same split is examined from the language side in choosing one language or several.

What are the real options for getting two languages to talk?

There are about seven, and they differ mostly in what happens when one side is slow, dies, or is restarted by somebody at a keyboard. At the simple end sits one language everywhere, which is not a failure of ambition and is often correct. Next come in-process bindings, where one program calls into another language directly. Then hand-written pipes, sockets or files between two of your own processes, which every team invents and every team regrets in a specific way.

Above those sit the middleware options. ROS 2 carries messages between processes in C++ and Python, brings drivers and recording with it, and is the default in most of the industry. A focused middleware such as HORUS moves messages between Rust, Python and C++ processes through shared memory on one machine, so nothing is serialised on the way across and the three languages read the same buffers. There is also the vendor SDK that arrived with your arm, which decides the language for you, and a network broker, which is the right answer when the two languages are on different computers and the wrong answer when they are not.

How do the ways of crossing a language boundary compare?

The comparison comes down to what each approach assumes you already know and what it does when one side misbehaves. Read the last two columns first, because that is where teams misjudge the cost.

OptionWho it is forWhat it assumes you knowWhen to pick itWhen not to
One language everywhereSolo builders and very small teamsOrdinary application programming in that languageThe whole robot fits comfortably in one language's strengthsThe control loop and the perception code want opposite things
In-process bindingsTeams with a heavy library they cannot reimplementBoth languages, plus how memory ownership crosses between themA single call out to existing code, made rarelyThe call happens repeatedly inside a control cycle
Hand-written pipes or socketsTwo processes, one machine, one afternoonEncoding your own messages and handling partial readsA throwaway prototype or a one-off bridgeThe bridge has outlived the prototype and grown options
ROS 2Teams needing drivers, recording and a hiring poolPublish and subscribe, launch files, a Linux workstationThe hard part is sensors, mapping or integrationEverything runs on one box and timing is the product
HORUSSmall teams whose Rust, Python and C++ share one machineYour three languages, and what your loop must not missProcesses on one computer must hand data over promptlyYou need mapping, planners, drivers and a large ecosystem
Vendor SDK languageTeams building on a bought arm, base or humanoidThe vendor's API and the assumptions inside itThe SDK already covers what the product doesThe SDK cannot express the behaviour you sell
Network message brokerRobots split across several computersNetworking, retries and what happens on a dropoutThe two languages genuinely live on different machinesBoth processes are on the same board

No row wins. The right row is the one whose final column does not describe your project.

Which languages should each part of your team write?

Give each language to the part of the robot whose failure mode matches that language's failure mode, then let team members follow the work rather than the other way round. Perception, task logic, the operator interface and the scripts that make the robot testable belong in Python, because those change constantly and their worst outcome is a wrong answer you can see. Motion, safety interlocks and anything talking to a driver belong in C++ or Rust, because their worst outcome is a machine that keeps moving when it should not.

The staffing implication is uncomfortable and worth saying plainly. If exactly one person on your team can work in the systems language, then you do not have two languages. You have one language and a single point of failure wearing a disguise. Two people minimum on each side, or the mix is a liability the first time somebody takes a holiday.

The good news is that the boundary makes hiring easier rather than harder. A Python-fluent applicant can be useful on the half of the robot written in Python from their first week, without learning the other half at all.

Does your hardware decide whether you can mix languages?

Often it decides for you, and the deciding factor is how much computer you have rather than how many. On a microcontroller with no operating system underneath it, there is one language, and the question does not arise. On a small board where everything runs together, a second language is possible but every extra runtime costs memory that the machine did not have spare, and an interpreted process competing with a control process for the same cores is a real problem rather than a theoretical one.

On a full board or an industrial computer, mixing is comfortable and normal. The interesting hardware question is whether all your processes sit on one computer or several, because that changes which options above are even available. Shared memory between languages exists only when the languages share a machine, which is the point covered in whether your robot needs shared memory.

The trap is choosing your language mix on a laptop and discovering the constraint on the robot. Put the second language on the actual target hardware in week one, running the actual ugly part, before anybody builds a plan on top of the assumption.

How much does your deadline change the language decision?

A deadline inside one quarter says add no new languages, and a long runway says the second language is cheap insurance. Introducing a language is not the cost people estimate. The cost is the packaging, the build, the deployment, the debugging story and the day somebody has to explain to a new hire why the robot needs two of everything, and none of that appears in the afternoon it takes to get the first process running.

Under deadline pressure, teams reliably make one specific mistake. They rewrite the piece that annoys them rather than the piece that is failing, because annoyance is loud and failure is intermittent. The rewrite lands the week before the demo, and now the demo depends on code with no history behind it.

If the deadline is real, the correct move is to keep the language you have and change the boundary instead. Splitting one overloaded process into two, in the same language, fixes a surprising share of what people diagnose as a language problem. Do that first. If the problem survives the split, it is genuinely a language problem, and now you know rather than guess.

What if nobody on the team has written C++ or Rust before?

Then stay in Python for now and spend the learning budget on the boundary rather than on a language. A team fluent in one language and nervous about another will write worse systems code than the Python they already write, and the failure mode of bad systems code is worse than the failure mode of Python. The arm does not stop before it hits the table because somebody's ownership rules were wrong in a language they were learning that month.

There is a real order of operations for teams in this position. First, get the robot working entirely in the language you know, and find out where it actually struggles. Second, make the struggling part its own process with a clear message boundary. Third, and only then, rewrite that single process in the systems language, with the old one still available to switch back to.

That sequence means the second language arrives on a small, bounded, already-understood problem instead of arriving as a whole-stack decision. Teams who invert the order tend to end up in the territory described in when Python stops being enough.

What does a bad language mix look like a year in?

It looks like a file everybody is afraid of. There is one module, usually named something like bridge or common or utils, that converts data between the two languages, and it has grown flags, special cases and a comment explaining that the ordering matters. Nobody wants to touch it. Two engineers have independently decided it is the source of the recurring bug and neither has proved it.

The second symptom is asymmetric knowledge. One half of the robot is understood by three people and the other half by one, and the one is the person on call. Releases wait for that person. Design discussions defer to that person. When the person is unavailable, the team works on whatever is not blocked, which is never the important thing.

The third symptom is the honest one. The team can no longer say which language a given behaviour lives in without opening the code. That means the boundary is not really a boundary, it is a seam that has been leaking for months, and the fix is to redraw it rather than to remove a language.

What do you give up by running two languages side by side?

You give up the ability to change a behaviour in one place, and it shows up as coordination rather than as difficulty. A change to a message shape now touches two codebases, two review processes and two release steps. Small changes stop being small, and teams respond by batching them, which makes each change riskier than the one before it.

You also give up a single view of what went wrong. When a robot misbehaves, the story is spread across two logs with two formats and two clocks, and reconstructing what actually happened takes patience nobody has during a customer visit. The mitigation is to agree on one timestamp source and one log format on day one, which costs an afternoon and saves a quarter.

The third thing given up is onboarding speed. A new engineer must learn not just two languages but two sets of conventions and the unwritten rule about which side owns what. Write that rule down. A team that can point to one page describing who owns which side pays this cost once instead of with every hire.

When is ROS 2 the better choice?

ROS 2 is the better choice whenever the hard part of your robot is sensing, mapping, navigation or integration rather than the join between two of your own processes. If you need a driver for the lidar you just ordered, a planner that avoids people, a way to record a failure in the field and replay it at a desk, and colleagues who already know the tool, ROS 2 gives you all of that and a Python and C++ story that has been production-tested by a large number of companies.

ROS 2 also wins on the plain organisational axes. You can hire people who already know it, vendors ship packages for it, and a contractor can be useful in their first week. Those matter more than most architectural arguments, and teams underweight them constantly.

HORUS is not the answer in those cases. A shared-memory middleware moves messages between languages on one machine and brings you no mapper, no planner, no driver and no ecosystem, so a team whose hard problems are perception and integration would be choosing to write all of that themselves. That is a bigger decision than a language mix.

Is Python always the part that has to go?

No, and here is why. Python is blamed for a class of problems that mostly come from where it was put rather than from what it is. A Python process reading a camera, doing detection and then commanding a motor directly is a design problem wearing a language costume, and the same code split into a detector and a separate commanding process behaves differently without one line of it being translated.

The genuine limits are narrow and worth knowing. Python pauses at moments you do not choose, which matters in a loop that must not miss its slot, and one interpreter attends to one thing at a time, which matters when several threads want to work at once. Neither limit touches a planner, a state machine, an operator interface or a test harness, and those are most of a robot's code.

So the rule is not remove Python. The rule is keep Python out of places that cannot tolerate a pause and keep it everywhere else, because the language you change quickly is where your robot gets better. The nuance is examined in what C++ gives you that Python cannot.

Does a second language make the robot harder to debug?

Partly, but not the way you think. The extra difficulty is not that two languages are hard to reason about individually. It is that a failure crossing the boundary leaves each side with half the story, and neither half is wrong, so both engineers can look at their own logs and conclude the problem is elsewhere. That is a coordination failure, and coordination failures take longer to resolve than technical ones.

The parts that genuinely get harder are stepping through a call that leaves your debugger, and reproducing a timing-dependent fault where two processes were scheduled differently on the day it happened. Both are real, and both have known answers: log every message crossing the boundary with a shared timestamp, and make it possible to replay a recorded run at a desk.

What gets easier is often ignored. Two processes can be restarted, replaced and tested separately. A crash stays on one side. You can run the systems half with a recorded input file and no robot present at all. Teams that instrument the boundary early usually find debugging improves rather than degrades.

How should a team settle this in a week?

Draw the boundary on a whiteboard first, then check whether the code already agrees with it. Write down every process the robot runs, what each one owns, and what messages pass between them. If a process appears in that list doing three unrelated jobs, the language question is premature, because the split is wrong before any language is involved.

Then run one experiment, not three. Take the process you argue about most, put it on the real target hardware, and feed it the ugliest real input you have. Watch what the robot does rather than what a chart says. Ask whether the arm still stops where it should when the machine is busy, whether a new engineer could reproduce the setup from a clean checkout, and what happens when you kill one process while the robot is moving.

Whatever you decide, write the boundary down in the repository next to the code, in a page short enough that a new hire reads it. The mix is not the risk. The undocumented mix is.

The HORUS Fit Framework reduces the choice to five axes: ecosystem size, setup effort, team size fit, deployment target, and licence. Score every option on all five, and pick the one that is not weak where your project cannot afford weakness. A related decision, whether to replace what you already have, is covered in when to rewrite and when to live with it.

If the three-language handoff on one machine keeps turning out to be your sticking point, put HORUS on your reading list rather than this quarter's roadmap: star it so it is in your list when you start building.

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