Rust, not related to the video game also called Rust, is a promising systems programming language with novel features ideally suited for game development. Exposure and awareness within the game developer community, however, remains limited. In this post, I provide a gentle introduction to Rust and attempt to justify its place on your radar.
What is Rust, and where did it come from? In this fantastic talk, James Munns gives us a detailed oral history. Way back around 2010, Mozilla was frustrated by the state of development in Firefox, a massive software project written mostly in C++. Despite best practices and an abundance of engineering talent, writing high-performance, parallelised, and memory-safe code, at that scale of complexity, remained fraught and error-prone.
Bear in mind, this predates the advent of C++11 (aka the 2011 edition) which heralded efforts to somewhat modernise the language. Even so, manual memory manipulation is easy to get wrong, and research from multiple vendors describes this category of error as responsible for 70% of security vulnerabilities.
Into this context steps Graydon Hoare, a Mozilla employee, introducing a potential solution to the roadblock: Rust, the hobby language he'd been tinkering with since 2006. In 2012, Mozilla would formally announce Servo, an experimental research project to re-imagine a browser engine built with memory safety and concurrency as first principles. And alongside it, Rust, the companion language to make it all possible.
These early days of Rust are described as a Cambrian explosion of ideas and wild experimentation. Concepts were liberally stolen from other languages, from C++ to OCaml, Haskell, Erlang, ML, C#, Ruby and more, reflecting the diverse pool of engineers working on the language at the time. Still, most in the industry, while admiring the optimism in taking such an ambitious moon shot, remained pessimistic about the prospects of success.
2015 saw a major milestone, with the release of Rust v1.0. Perhaps as significant as the feature list, was the number of failed experiments left behind on the cutting room floor, the team unafraid to pare down the language to its quintessential elements. This was also the first time stability guarantees would be offered, a quality notoriously absent before.
Soon after, in 2016, Firefox shipped its first production Rust code. The industry and community started to take notice, and Rust began its impressive, and as yet unbroken,
four five year streak as Stack Overflow's most beloved language. [Thank you James Munns for pointing out it's now actually five years].
Right from the outset, Rust set out with a clear focus on building an inclusive community. They, in turn, have contributed to Rust's impressive technical aptitude, but have also fostered a sense of reverence and fondness not often witnessed in other languages. Are they crazy zealots or onto something?
The performance of C++ with the convenience of C#
This was the first time Rust hijacked my attention. C++ enjoys a long-standing ubiquity, in part, due to its ability to express zero cost abstractions. As explained by Bjarne Stroustrup, the creator of C++:
What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better.
It's easy to spot the relevancy to games. Making frame-rate while simulating entire worlds is a daunting performance challenge. Indeed, C++ underpins the bulk of game engines. There simply is no other industrial language that offers the same speed and low-level control, whilst writing programs in the large.
C++, however, suffers from the weight of its legacy. The accumulation of features over 40 years makes for a complex and intricate language. In the last decade modernisation of the standard has done well to uplift it from its C roots, but the experienced programmer must build up an arcane lore of which features are blessed, and which machinery is dangerous. As Stroustrup again describes:
Within C++, there is a much smaller and cleaner language struggling to get out.
This makes the language daunting and difficult to approach for beginners. In this blog post, Rust contributor withoutboats defines an import quality about abstraction:
A zero cost abstraction, like all abstractions, must actually offer a better experience than the alternative.
So yes, of course, C++ offers a better time than your own hand wrought assembly. However, this is making the subtle point that it's competing against a secondary force: a more expensive abstraction that justifies its cost by being more comfortable and convenient.
We see this writ large in the rise of popular game engines that eschew the complexity of C++, the most notable being Unity. End users write code in C#, a more forgiving and ergonomic language, creating a boon in developer productivity and a reduction in iteration time.
In large codebases though, near the edge of the performance envelope, this trade-off begins to bite. The garbage collector eliminates an entire category of errors by removing responsibility for memory management from the end-user. However as its workload grows, so do periodic performance spikes antithetical to smooth gameplay.
The experienced developer can still create a performant experience, however, this demands plugging the leaks in the abstraction. They must build a mental model of the machinery behind the curtain, a collection of arcane wisdom that bans many of the original conveniences, lest they disturb the garbage collector.
So development teams face a choice. Better resourced AAA studios generally choose Unreal or in-house engine tech built on C++, able to absorb the overhead for long term gain. Less resourced studios optimise for time to market, choosing Unity, or one of the many other accessible game making tools (Godot, Haxe, Game Maker, etc.). They often postpone performance concerns until after business eligibility is secured.
Rust, however, for the first time, promises a third way. A world where it's possible to write zero cost abstractions without sacrificing higher-order elegance.
To understand Rust's special sauce, we're going have to talk about ownership and how it handles memory. This is only a simple sketch, but in-depth resources exist for the curious.
Writing optimised code is often about taking the way we, as humans, naturally think of an idea or algorithm, and instead expressing it in terms that favour the computer. This act often harms the legibility and understanding of a program, which makes it much harder for us, the humans, to reason about its correctness.
In a manually managed language, like C, the hapless programmer is left responsible for the machinations of the machine. They must take great care to ensure data is appropriately loaded into memory before operation, and then responsibly disposed of afterwards. A difficult dance in which missteps either cause dramatic crashes or else subtle and hard to detect vulnerabilities. But these are the very same tools that allow careful users to tune performance.
At the other end of the spectrum, garbage collection promises the programmer it will automatically deal with the problem on their behalf. They are now free to express code naturally, but in doing so, it ties hands behind their back. They no longer have, at least not without indirection, the levers needed to wring out maximal performance.
Rust begins from a different premise. Rather than hiding this complexity, it accepts that computers are hard for humans, and instead tries to save us from the dangerous bits. Users can still tune the machine, but with less rope to wrap around their necks.
In the same way that static typing exists, very clever people have figured out how to make the compiler eliminate a whole category of memory and concurrency errors. To achieve this, Rust makes a bargain with the developer:
"I'm going to keep track of the lifetime of every piece of memory in your program for you. This way, I can detect the moment you're no longer using it and safely free it on your behalf. But in return, I'm going to need you to follow strict rules about the ownership of that memory. If you try to use it outside of the scope that owns it, my humourless friend here, the borrow checker, is going to make sure you don't hurt yourself."
However, like static typing, this lunch isn't free. Rust is known to have a steep learning curve, "fighting the borrow checker" becomes a right of passage. It takes time to learn this new paradigm. Ownership makes some familiar patterns difficult or impossible and demands new ones be learnt in their place. Perhaps we should revise our earlier statement as: "The performance of C++ with the
convenience safety of C#"
To a developer standing on the shores of 2010,
git, a new version control system with a steep learning curve, may have seemed like a risky investment. But, in the ensuing world of Github, it's hard to argue the effort was wasted, even if some workloads (i.e. large games) still require alternatives.
In a similar vein, how can we qualify Rust's popularity as a meaningful signal? Ultimately, we will only know by the volume of mud we've dug through in the trenches, and admittedly, it is far too early to collect this data for games.
In other industries, though, early reports of Rust are effusive. Mircosoft, Facebook, Amazon, Dropbox, Cloudflare all have Rust deployed in production. The Linux Kernel and Chrome teams have at least investigated pathways towards integrating Rust. These companies aren't looking to throw out multi-million line codebases and rewrite everything, but they are, where it makes sense to do so, choosing Rust for self-contained new projects. At a systems programming level (operating systems and web browsers) Rust does seem to be competent and desirable.
It's also garnered interest up and down the entire stack, from bare-metal microcontrollers to web applications deployed via web assembly. Rust has done very well to make it's ecosystem comfortable and welcoming. Compiler tooling, documentation, error messages, cross-platform support, modules, dependency management and packages are all first class.
To anyone booting up a commercial game engine, this might not seem like a novelty. Still, to the beleaguered operating system kernel developer, sequestered away writing C, it's hard to overstate how exciting the chance to have nice things must feel.
There, however, exists another axis on which we should be interested in Rust. As a systems developer, Bryan Cantrill, explains in his highly entertaining talk, with enough experience and care, it is possible to write well behaved C code. But Rust excites him because, as well as memory safety, it unlocks new abstractions.
Better abstraction, of course, is the raison d'etre of C++, and why it was bolted onto C in the first place. But, even in comparison to C++ or C#, there is real innovation here. Mainstream programming may have rejected hardcore functional programming languages like Haskell, but it hasn't stopped the best ideas from percolating into the groundwater of other languages.
Normally this manifests as bolted-on language extensions, one example being
LINQ in C#.
LINQ gives developers a concise declarative language to query and manipulate data. Compelling, except, as a source of garbage collection pressure, game developers soon learn to steer clear. Rust, however, was built with these inspirations from day one. Rust iterators, the equivalent of
LINQ, are available as zero cost abstractions, closing the gap between how humans think and what the machine expects.
Functional programming is, at its heart, about creating contracts to avoid unintended consequences. This DNA is apparent all over Rust, the quality of "correctness" permeates far beyond the memory model. If you're working on a complex project or with a large team, this should be a strong selling point. Immutability is on by default. Strict read/write rules on mutable data allow Rust to promise fearless concurrency. While the type system departs from C++ and C#, there are no inheritance-based classes, for example, it broadens the scope of what can be verified. Often if it compiles, it runs.
Swift and Kotlin, two other modern and well-reviewed languages, perhaps signal these are ideas whose time has come. Whilst each has different memory models (Swift is C++ like, and Kotlin is garbage collected), they represent the state of the art on their respective platforms (iOS and Android). Although to different degrees, like Rust, both have a hybrid semi-functional flavour to them.
Rust is still an immature language. Today there are still useful features in C++ that Rust, as yet, cannot match. However, this is not a static target. With the groundswell and energy around it, and new language release every six weeks, this gap has been closing rapidly.
Perhaps of more concern is the lack of maturity in the broader ecosystem. For example, anybody building a desktop application today would be frustrated by the lack of de facto GUI (Graphical User Interface) tools. Building new GUI systems is an elaborate multi-year effort, but here the vibrancy of the package ecosystem and community shines. There are already tens of thousands of crates (i.e. libraries) listed on the package registry. Though an overabundance of choice can ultimately become its own burden, there are already enough building blocks available to begin answering these questions. Indeed, several early-stage GUI libraries are vying to do so.
This illustrates the biggest hurdle to adoption. Modern game engines are a vast orchestration of different technologies, of which the choice of programming language is only a small piece of the pie. Building a mature engine, from scratch, might be a decade long endeavour. We could easily say the same for multiple sub-technologies, for example, rendering, networking, physics and audio middleware.
It's best to reuse what we can. Rust has some advantage here, calling native C code from high-level languages, like C#, can be expensive. Rust behaves more like C++ though where the overhead is negligible. However, most middleware is written in C++, not C, and there is no simple bridge to call C++ code from Rust. That's not to say inter-op is impossible, C can be used as a lowest common denominator to glue the two together, an operation that demands a strong stomach.
However, as with any common denominator, does the trade-off eliminate the safety we sought in the first place? Ultimately native solutions would be preferable. Here, again, the ecosystem is already at work to provide many of the building blocks. I had initially assumed graphics programming would be one of the last holdouts, where vfx are built with rudimentary C-like shader languages. But even here we see promising Rust based alternatives. An innovation that might, ironically, be a pathway for Rust into existing engines.
Disruptive technologies often aren't successful on their technical merits alone. In the case of
git, Github was the killer application that allowed it to out-compete rival distributed version control systems, and ultimately disrupt the sector as a whole.
In the case of games, the killer application is almost certainly going to be a game engine. Unity had many compelling features: the ease of use and price point was a revolution in democratising game development. But it was also uniquely positioned in time as the best way to access the exploding iOS app store. Unity built on that success and mind share to eventually compete and disrupt far beyond just the mobile space.
Mind share is important; the virtuous cycle that builds a moat around a technology. Years of institutional knowledge keeps teams invested. It maintains their access to a broad talent pool and maximises chances new a platform or middleware vendor will prefer their chosen engine.
Rust shows promise as a compelling language for engine development, and there is no reason this should be any different for programmers at the business end of large or complicated in-game systems. However, not every member of a development team is pitching at this level, or even at all. To a level designer making scripted gameplay content, building interactivity with Rust might be overwhelming.
Other engines resolve this dissonance by providing simpler interfaces. Either a simplified scripting language like Lua or else visual programming like Blueprints in Unreal. To an artist though, they'd be far more concerned with the efficiency of the art pipeline.
It's not that a Rust flavoured game engine can't solve these concerns, but building a robust user experience stretches beyond the programming language. It's plausible Rust will engender enough programmer goodwill to drive adoption, we are an opinionated bunch, after all. However, it seems more likely a new game engine will secure its future with unprecedented democratisation, either of new workflows or new markets.
Predicting specifics would be an act of fortune-telling. However, I can offer some educated guesses:
- After a long burn, the open-sourced Blender has begun disrupting 3D content creation software. It's reasonable to expect a game engine might follow suit; however, Godot already has a significant first-mover advantage. Early efforts would be competing directly with Godot rather than the incumbents.
- Despite the advent of accessible game engines, the expert skills required to produce content remain labour intensive, and therefore expensive. Procedural content and AI-assisted art tools have tremendous potential to usher revolution here, as seen in early research. However, this innovation is broadly available to commercial engines too, and we already see the first fruits ripen.
- Increasingly, chart-topping games are expected to provide large multiplayer shared worlds. Networking is already hard, but large scale distributed computing is difficulty on hell-mode. We've seen massive venture-backed plays in the space, hoping to provide developers on existing engines an easy onboarding, so far with mixed results.
On this last point, Rust may be able to differentiate itself technically. Fearless concurrency and functional programming are eminently well suited to distributed computing. Web platforms adopting Rust can only further this cause. A game engine that better navigates this distributed world will have a significant advantage. Though it's just as likely any eventual winner will embody all of the above and more.
Immaturity can be fixed with time and effort, but escaping the scrapheap of obscurity is much harder. Nobody wants to be stranded on a sinking island. Whilst it's impossible to say how long Rust's tail will be, the same is not true for proven technologies. Even if everyone stopped writing C++ overnight, code written in the language would continue to exist for decades to come.
This year Mozilla, in reaction to their precarious financial position, laid off scores of developers - including the entire Servo team (the experimental browser engine paired with Rust). There was a time when this could have been the death knell. However, it seems, there is enough resilience for the language to survive and prosper. With investment from industry, some teams finding new homes, and the announcement of an independent foundation, it does seem we're witnessing a dawn rather than a twilight.
Whilst the incremental growth of mature technologies isn't as exciting or electric, they aren't standing still either. The C++ language specification evolves at a glacial pace, but there is nothing to prevent C++ from, ahem, borrowing some of Rust's innovations in memory safety. That doesn't clear the table of C++'s legacy features, but it might be good enough for some.
It is also true that Rust is the first language of its kind. We probably don't know how to make a simpler systems language, but not every user, for example, needs to care about the default integer size on an obscure micro-controller. A plethora of garbage collected languages exist, in the same way, we may see Rust inspire languages of the future. Perhaps one of them will package ownership-based memory into pragmatic and sensible defaults that prove wildly popular.
Or we may see innovation that's got nothing to do with Rust, but delivers on the same promise. For a long time, Unity has been doing creative things to improve the performance of C#. Their latest effort, DOTS, in part imagines a limited subset of C# as input for a custom high-performance compiler. However, this franken-monster approach means rebuilding the engine from within and hoping the user base doesn't notice too loudly. The full story is more involved than that, but it's an ambitious bet that might pay off.
By painting a realistic picture of Rust's roadblocks, it may seem I'm down on its prospects as a mainstream language for game development. Indeed, if you were launching a new commercial venture today, I would be the first to point you to Unreal or Unity.
However, when I consider the future, and the code I'm going to be writing for the next ten years, I'd much rather be doing that with Rust on my side - or at least something that looks like it.
Whether my optimism is naive or not, my gut, and my experience of the Rust community's vibrant enthusiasm, tells me that we are heading towards a tipping point. The threshold beyond which Rust was nowhere and then suddenly it is everywhere. For games, that day might still be far away, but I believe many of the technical hurdles will fall: because smart people enjoy a challenge and because it is there.
How exactly this future arrives, no one can say, but I can point you at the efforts underway to bring it about. If you want to write code today in a well-featured game engine, you can already do so by using Rust bindings for Godot. However, these efforts often lose a lot in translation, and I'd be surprised to learn otherwise here.
The ideal would be a game engine built for Rust. Enter Bevy, an open-source engine that recently splashed onto the scene, to much fanfare from the community. By building on the lessons of Amethyst, a prior Rust game engine, Bevy seems to have struck an ergonomic chord and generated a lot of enthusiasm. It is still very early days though, and not yet ready for production. In the near to medium future, I can imagine it being suitable for lightweight mobile games and the like.
Perhaps the best contender for building a heavyweight Rust game engine is a AAA studio. I find it hard to imagine established studios throwing out their existing tech, but a new entrant, free of any legacy, would be well placed to invest into Rust. At that massive scale, even a slight competitive advantage can be worth paying for. As games-as-a-service becomes the norm, any language that facilitates rapid refactoring and content churn is going to be appealing.
And indeed, we can already see Embark making a move in this space. Founded by Patrick Söderlund, former head of Dice and EA executive, we can tease from job postings that their first commercial title is sensibly made in Unreal.
However, Embark have come out guns blazing for open-source. Already they are significant sponsors of Blender and Bevy, as well as many other smaller projects. But they aren't sitting on the sidelines, and have already published several of their own contributions. They've made it clear they are working on Rust tech internally. Time will tell what exactly they are brewing.
An exciting space to watch.
If you're interested in learning more about Rust I can recommend the following resources and communities: