"Don't write games in Rust" and how fast and easy can you access mutable data?

I've listened to Better Software conference, and once again heard people say "don't use Rust, it slows you down, you have to fight with it."

I guess part of it comes from the fact that in C you can both keep references to structures, and mutate them, which is impossible in Rust. I haven't done game development, and wonder where you need these references to mutated data?

In Rust, the obvious solution is, to use vector/array indices instead of refs. Accessing vector or array by index generates range check in assembly code, which may cost some cycles, and if your game needs to do this lots of times every frame, it becomes a problem.

I tried imagining a case where you'd really need lots of references, to access tightly connected data, and wouldn't want indices. I can think only of a game like Noita, where we simulate many particles, and have to access neighbor cells very often. I guess in this case, when the data doesn't move, and you process the cells in a single thread, why not store fixed references and skip the checks?

I wonder if working around this limitation is just a skill issue, or not.

So I thought of workarounds, and wanted to see how heavy or light they are. Here's a simple experimental code in Godbolt with 4 approaches:

  1. read data by reference
  2. get mutable access by &mut my_vec[i]
  3. same by vec.get_mut(i).unwrap_or(|| MyError)
  4. unsafe cast usize into an immutable reference

Reading a reference takes 2 lines, #3 produces 5 lines of assembly code. To my surprize casting a reference produces almost 20 lines.

Some unknowns:

  1. Are the functions and the assembly code representative of what we'd get in the real code (where they're not isolated in separate functions)?
  2. I managed to cast to an immutable reference, but how to cast a mutable one?
  3. What's the C equivalent of reading or writing to an object by reference, stored in another object? How many lines of assembly does it generate, if any?
  4. What are other options? Let's assume the simplest case to begin with: we have fix-sized arrays, and they don't move, e.g. may even be Pin.

Actually, this is a great example of a situation where you should not store pointers at all and should use indexing. In any kind of situation where you have a grid of simple cells, storing pointers massively increases the amount of memory used per cell, and thus increases the amount of memory that has to be read and written. This is far more important to performance than a bounds check.

They are not. At this scale, the individual lookups are likely to be completely rewritten by the optimizer. You can't judge anything about performance using these functions in isolation; you need to investigate the performance of the whole algorithm which contains the lookups.

Not finding bugs early is a good way to slow you down.

When you find range checks that are not optimised away and after profiling and don't have safe way to recode, reach for unsafe. one example

This is a philosophical thing more than a technical thing, so isn't really something where you can change people's minds.

In game dev in particular, the culture has long been bug mountain development where nothing works for ages then you crunch to get it "good enough" to ship and call it a success when playing for an hour "only" crashes 95% of the time. Famous devs talk about how they don't think memory safety is important in single-player games because crashes are fine.

Rust absolutely makes that harder, and while obviously most people here who've bought into rust will say "good", if that's what someone wants it's really not worth the effort to try to convince them otherwise.

(My personal hypothesis is that a ton of this cultural setup happened when people were using terrible compilers where you legitimately did have to optimize a ton of stuff yourself and we'll need that generation to die off to change it.)


All that said, remember that really rust's superpower is fearless concurrency. If you want to make a simulation game that actually scales -- and thus can't just use a Ball of Mud design pattern -- then Rust might be a great choice. That's where things like automatic system parallelism can be a huge win and the "do an immutable pass first to collect work, then apply that work" structure you generally need to not introduce concurrency ordering bugs also happens to work amazingly well with the borrow checker. If you just want to make a single-threaded vampire survivors clone, though, you're not going to get any advantage from all that stuff. If you don't need what Rust's good at -- especially if you de facto have to use Unreal anyway -- then using something else could well be the practical choice.

The gist I've gotten from what actual game devs have said is Rust should be great at engine development, but game logic and scripting is generally very on-the-fly, continuously tweaked until the ephemeral "feels right" has been achieved. But that's hardly any different to the current state of the art where engines are in C++ and scripting is in Lua/C#/Unreal Blueprints/GDScript.

The more interesting thing Rust could, in theory, bring to the party here is being able to more reliably make large changes to the engine late in development, or perhaps simply having a functional packaging system making it a lot more feasible to Lego brick your way to a high quality engine. There's a lot of good stuff going on in that space already, but I feel like there's a lot still to go.

I think the current, though slow, push engines are making towards data-oriented designs will eventually quiet some of the belief that you need persistent pointers between mutable objects to do anything, but we're probably talking decades there still before it becomes any sort of default.


Out of curiosity, what BSC talk was this? The only one on the YouTube channel that looks like it is appropriate is the Ted Bendixson one with the inflammatory title :face_savoring_food: - the mention of Rust in that talk (around 26 minutes) is in the middle of a section about, essentially, that you should be prototyping your game ideas first and at that point all the "reliable software" rules go out the window and who cares about tests and memory access patterns, you're trying to find a game. I'd say the extreme position there is you could build it in JavaScript in the browser!

They later in that section say after you find the game and you're done prototyping, tests and Rust are potentially useful, just with an implication that it's probably overkill - I'm guessing because most games, at least in the past, had a shelf life of about 4 weeks post-release. Nowadays, being able to have a team maintain a game over a decade is a lot more valuable (though not nearly as valuable as many executives thought, but Live Service would be getting pretty off topic), so maybe there's a stronger argument for that rigor in your game than previously.

So it boils down to: how do I store reference to something and mutate this "something" from other piece of the code (e.g: a hero position in a game world)

The answer is usually either cell (which is not your case) or array/vector+index (which is). It is pretty much the same thing as pointer arithmetics in C, and as it is being said in one of comments above, you could use unsafe to disable bound check, and I hope that index without bound check should be as good as C pointer.

Yes, I think it was that one. Apart from the conference, the community (Blow, Muratori et al) occasionally drops similar remarks, and Blow had a small rant in his streams. So I can say it's an established opinion in this game dev community.

That's why I wanted to take a case, which is most convenient to their point, and inconvenient in Rust, and compare their C baseline, and possible Rust approaches.

I'm not surprised by Blow (definitely some nominative determinism there!) but I don't recall Casey saying all that much about Rust? I know he doesn't use it but the impression I get from him is he'd be more concerned about practicalities like build integrations and header-only libraries than any particularly strong opinions about the language itself.

(does some Kagi-ing...) Apparently he has some age-restricted rant on Twitter I'm sure as hell not going to make an account for? It might be this section from Handmade Hero I found later that's mildly inflammatory sounding:

the rust borrow checker like all that stuff those are for people who are still not very good at programming right there's they're in a stage of architecture that is very limited and not appropriate for modern high-performance well-architected code

But even it doesn't really make sense since he's talking about using arenas for allocation instead of smart pointers... and the borrow checker is god damn incredible at arenas, you could even call it the only thing it's actually good at (rather than just sufficient) other than preventing return &local

I'm probably going to put that in the bucket of "he's just not that interested in the language and hasn't really spent time thinking about it" - at least as of 5 years ago.

(edit: Just to be absolutely clear: this is perfectly fine, he's completely entitled to his preferences, and a single off-hand reference once in five years is hardly a vendetta!)

I wouldn't worry too much: we would go from “Rust is not suitable to do gamedev” expressed by “Important people in the gamedev” to “Rust is the gamedev language” expressed by “Important people in the gamedev” without anyone ever changing their opinion. Blow would still say that Rust is a bad fit, Carmack would tell us that Rust. not Firefox, is Mozilla's greatest industry contribution… people would just change the opinion about who is the important one.

After all games arrives on Windows later than all other types of programs and now, for the exact same reason they are the last holdouts… for some reason people believe the gamedev is ahead of the curve, but it's usually the opposite.

Nah, I'm just curious which specific arguments the OP is concerned about. Casey tends to be pretty level headed for how outspoken he can be, so even if he's not right you should still pay some attention (unlike Blow, who like DHH seems to pick his opinions largely on how much he gets to be righteously angry and superior at someone).

Unfortunately Carmack hasn't really been relevant for about 20 years now, so I don't think any positive feelings he might have will have much influence, only shipping games and doing talks is really likely to move the needle there; but there's clearly plenty of interest out there, so it'll eventually get there if it doesn't get it's lunch eaten by something else (maybe Odin adds a borrow checker, I dunno)

Yes, but talks follow the switch, not precede it.

And Blow and Casey for 10… that's precisely the story: people who are designing the future don't have time to also spend it on talks and educational videos.

Educational videos only start talking about new technologies after they arrive and become popular in the industry.

And usually a different people are doing them in the accordance to the already mentioned principle. When and if Blow and Casey would start accepting the fact that some people may use Rust instead of C++ (while still pushing their own, increasingly irrelevant, alternatives ahead of both) industry would already declare C++ “legacy”… but that wouldn't happen tomorrow.

Another 5 cents here, but I agree that the complaints I have seen (that aren't about ones taste in programming languages) usually relate to iteration speed and organizing data, as mentioned here. Someone who wants to try a lot of different ideas quickly and not think about how the data is structured will probably not enjoy using Rust for it. Game development tools and libraries are also very C and C++ centric by tradition, so there may also be an ecosystem factor involved, unrelated to the borrow checker.

Games aren't exactly immune to the problems Rust is designed to prevent. If anything, they are more susceptible due to how interconnected they are. There are ways of dealing with that complexity that embody some of the borrow checker's rules. For example, an Entity Component System (ECS) may be set up to prevent simultaneous reads and writes. Non-pointer IDs (like generational indexes or hashes) are usually fine too, and even useful. It's not the end of the world of they become stale and they are easy to serialize in a save file.

I would think of Rust as a C++ alternative and use it for core systems and simulation. Then use a scripting language with hot reloading, like Lua or GDscript with Godot, for the "high level" parts. Something can start out as a script while iterating and prototyping, then get moved into Rust code later if necessary. I would probably not want to use Rust for exactly everything unless it's a very small game.

Neither of them champions C++ as far as I can gather from watching many of their presentations. Blow of course has spent 10 years or more building his own language to do what he wants to do the way he wants to do it. I think that is quite admirable. Casey has a lot negative to say about most features in C++ and describes how he only uses a very small subset of it.

Both of them have a crusade for performant software which I think is a good thing.

To answer your questions: it optimizes very nicely. References behave like pointers at the lowest level. You can get even better optimizations (fewer re-reads, more autovectorization) than C pointers thanks to Rust's stronger immutability and exclusivity guarantees.

Borrow checking is more like mandatory static analysis of the program. It's a compile-time check, not a runtime thing.

But the bigger issue is that many patterns used in C or C++ wouldn't be done in Rust, because of coding style. In Rust the question isn't a direct "how to mutate through a web of arbitrary pointers", but more like "how to avoid pointers" or "how to avoid arbitrary mutation", and in gaming the answer is ECS.

Rust does front-load a lot of effort to get the code right on the first try. In game dev it may create friction when you just want to try something out as quickly as possible, even if it crashes.

OTOH game dev takes many years and some games keep being maintained for many more years. It's hard for me to believe that all that time is spent on tweaking prototype-quality code.

It would become “admirable” if he would actually produce something people would like with that language and others would adopt it (or ideas from it). At this point it just proves that his past successes gave him enough money to spend his time on something that is not directly related to game making.

That's the issue with most these “influential personas”: they certainly may teach you something valuable about the past… but very few of them can give meaningful estimate about things that happened after they stopped producing things and started talking about things.

And Rust haven't existed in times when they were active thus their opinion about Rust are not relevant. The problem here is that people who would be relevant in an era when Rust would or wouldn't become a gamedev language are not writing videos right now, they are too busy coding! We don't know their names yet, thus couldn't use their words as guidance — and that's entirely unacceptable situation for the majority of population: very few people want to think, most just want to find some authority they would follow, instead.

But that's not Rust exclusive problems, all new technologies pass through that stage… some become important, some not, but if you look back… corellation with “this is obviously important technology” and “this is technology that would be used 30-40 years from now” is pretty damn poor.

Case to the point: which CPU architecture that was important 40 years ago is still in use today? Right: IBM/360, 8086, ARM. That's it. Where are these promising M68K, PA-RISC, SPARC and others? They made a splash and died.

Similarly with languages: 40 years from now C/C++ would still be with us (in a role similar to IBM/360 descendants: people would be split between sones who would forget about these and ones who would dream about ability to forget about these), but chances of new language (made by Blow or anyone else) arrive and become a replace C/C++ appears low.

It was a good thing in a world where hardware was underpowered (compared to what games needed from it) and where most successful games were both from a hardware or software tricks.

Today games are incredibly complex and most successful games are incredibly wasteful. That's not a good thing per see, but ability to write performant software and ability to develop engaging game rarely reside in one person, they only appeared to be related in an era where inefficient game was simply unplayable.

The talks I'll talking about are GDC and SIGGRAPH - those absolutely are bleeding edge, active developers, and from the relatively sparse selection I've viewed there Rust comes up there more than you might think already (more GDC given the focus) though I'd hardly say it's a hot topic. Generally it's things like data-oriented design showing Bevy's DX as an example to aim for.

It's getting a bit off topic, but I don't really buy that performance doesn't matter for games (the proliferation of performance modes alone in more recent console games says otherwise), or that game design has ever not been a separate skill to implementation, even if there are a few people (both now and earlier) than could do both.

To try and bring it on topic: Casey is an interesting example because most of his recommendations for getting high performance in games are pretty much exactly what Rust makes the easy path. It certainly seems like using Rust for gamedev would be something he would be in favor of, by that measure at least. Likewise we are still(!) hearing about engines attempting to add threading to their implementation, something considered very high risk and effort, due to it being basically the last avenue for scaling performance: Rust seems like a really obvious fit there too. It seems like we should be hearing more about Rust in gamedev than we do (certainly we hear a lot from the Linux kernel, web dev, hackers and everyone else on the planet using Rust!)

But I'm not actually a gamedev, just someone interested in it: I can't really say if it actually is a good fit for game development (even just in the engine), only take a guess, and judge if an argument about why it would or wouldn't work seems to hold water to me.

One thing I will say in terms of my experience, is that integrating cargo with an existing CMake build is kind of a pain in the butt, and editor support for mixed projects is pretty rough. It would be great if there was some more polish in that area, and I suspect that has more of an effect than anything to do with the borrow checker for adoption! I feel like there's enough pressure from outside games that that will get there sooner rather than later, though.

Note that that's rarely the kinda of performance we're talking about here in terms of things to which Rust's static checks are related. Mostly it's about reducing graphics card load to reduce frame times: using simpler LoDs, using lower-resolution textures, using less-accurate shaders for ambient occlusion, rendering to a smaller buffer then upscaling to the screen, etc.

Having two modes in the game is basically coming from the same forces that result in two tiers of console in the same generation now. (Plus ≈everyone just shipping on PC too and thus needing to have these knobs anyway.)

Yeah it's more in the sense that it's a signal that developers and players definitely care about performance (or at least developers think they care enough for it to matter).

I am just guessing, but I wonder if they are more saying "Rust is a complex language, learning it and learning how to use it takes a long time" and that is why it "slows you down". It would all depend on the individual, but that could easily be true for many individuals. Especially using references (rather than less performant smart pointers) can be quite a puzzle sometimes. Figuring out lifetime annotations and the correct design in a complex situation isn't trivial. And then if you change the design, you have more work to do.

This is what the get_unchecked and get_unchecked_mut functions are for.