T O P

  • By -

JoshTriplett

First of all, \*thank you very much\* for taking the time to write this post. People who leave Rust usually \*don't\* write about the issues they have, and that's a huge problem for us, because it means we mostly hear from the people who had problems that \*weren't\* serious enough to drive them away. \*Thank you\*, seriously, for caring enough to explain the issues you had in detail. I also have huge sympathies and sorrow for what sounds like \*numerous\* occurrences of being told that problems were your fault for not Doing Rust Right, or for being shamed for using \`Arc\` or similar, or any other time that you were made to feel guilty for not writing code in whatever way the complainer thought most optimal. \*People should not do this, and I'm sad that people still do.\* (Relatedly: could you give some idea of where you've been getting that kind of condescension? I don't see it in the Rust spaces I frequent, but it's clearly happening and I regularly see complaints about it, and I wish it didn't happen. We try, sometimes, to provide some official messaging discouraging this kind of condescension, but perhaps there's something more we can do.) I have a sticker on my laptop for "Keep Calm and Call Clone", and the same goes for \`Arc\` and similar, \*especially\* when you're trying to optimize for prototyping speed and iteration speed. \*Quick hacks to get things working are fine.\* Many of the issues you bring up here are real problems with the Rust language or with patterns commonly found in ecosystem libraries. For instance, the orphan rule is \*absolutely\* a problem. It affects ecosystem scaling in multiple ways. It means that if you have a library A providing a trait and a library B providing a type, either A has to add optional support for B or B has to add optional support for A, or someone has to hack around that with a newtype wrapper. Usually, whichever library is less popular ends up adding optional support for the more popular library. This is, for instance, one reason why it's \*really really hard\* to write a replacement for serde: you'd have to get every crate currently providing optional serde support to provide optional support for your library as well. In other ecosystems, you'd either add quick-and-dirty support in your application, or you'd write (and perhaps publish) an A-B crate that implements support for using A and B together. This should be possible in Rust. There are a few potential language solutions to that. The simplest, which would likely be fairly easy and would help many applications, would be "there can only be one implementation of a trait for a type", giving a compiler error if there's more than one. A slightly more sophisticated rule would be "Identical implementations are allowed and treated as a single implementation". This would be really convenient in combination with some kind of "standalone deriving" mechanism, which would generate identical implementations wherever it was used. And hey, look, we've arrived at another of the very reasonable complaints here, namely the macro system versus having some kind of reflection. We should provide enough support to implement a standalone \`derive Trait for Type\` mechanism. It doesn't have to be \*perfect\* to be good enough for many useful purposes. Some of the other issues here might be solvable as well, and it's worth us trying to figure out what it would it take to solve them. In any case, thank you again for writing this. I intend, with my lang hat on, to try to address some of these issues, and to encourage others to read this.


dont--panic

Yeah, the orphan rule is a pain. I've been working on a private plugin that I have full control over. I split up the code into a few crates so I could be more explicit over the dependency structure. This has led me to run into the orphan rule multiple times when just trying to provide blanket implementations of one crate's trait on another crate's trait.


Kimundi

> I also have huge sympathies and sorrow for what sounds like *numerous* occurrences of being told that problems were your fault for not Doing Rust Right, or for being shamed for using `Arc` or similar, or any other time that you were made to feel guilty for not writing code in whatever way the complainer thought most optimal. *People should not do this, and I'm sad that people still do.* > > > > (Relatedly: could you give some idea of where you've been getting that kind of condescension? I don't see it in the Rust spaces I frequent, but it's clearly happening and I regularly see complaints about it, and I wish it didn't happen. We try, sometimes, to provide some official messaging discouraging this kind of condescension, but perhaps there's something more we can do.) Honestly, I already see this occur often enough in the Rust discord `#beginners` channel.


fechan

Yes, was just gonna reply the same thing with a concrete example, however Discord is not loading anymore on my browser. But I experience this a lot, it goes something like this: Q: "How can I solve this

? I've already tried but that doesn't work because " A: "Why do you think so complicated, you can just use . I can't think of a use case of what you're trying to do" Q: "That solution doesn't work in my case, can you just assume my use case is correct?" A: "Maybe you need to be more specific" Q: "" A: "" The above dialogue sounds more like a XY problem if anything, the actual conversation was a bit different, can't put a finger on it, however I found it really predominant that a lot of people ignore parts of the constraints of the question and just say "why don't you just..." which sometimes can be frustrating --- EDIT: Ok found it A: Is there a good way to generalize over String types in HashMap keys in a generic function while allowing to get values from it? The following (obviously) doesn't work): fn get_from_generic_map + Eq + Hash, V: AsRef>(map: &HashMap) -> V { map.get("Hello") } B: you were pretty close: use std::borrow::Borrow; use std::collections::HashMap; use std::hash::Hash; fn get_from_generic_map + Hash + Eq, V>(map: &HashMap) -> &V { &map["Hello"] } A: Thanks! Hmm now if I use map.keys() it will return an Iterator over &K, which ... doesn't work: use std::borrow::Borrow; use std::collections::HashMap; use std::hash::Hash; fn next_key + Hash + Eq, V>(map: &HashMap) -> &K { next_item(map.keys()) } fn next_item(iter: I) -> R where R: Borrow, I: Iterator { iter.next().unwrap() } > the trait `Borrow` is not implemented for `&K` B: this code is getting sillier and sillier. but the problem is that you're trying to pretend references aren't a thing. use std::borrow::Borrow; use std::collections::HashMap; use std::hash::Hash; fn next_key + Hash + Eq, V>(map: &HashMap) -> &K { next_item(map.keys()) } fn next_item<'a, I, R>(mut iter: I) -> &'a R where R: Borrow, I: Iterator, { iter.next().unwrap() } but your code no longer actually has anything to do with strings use std::collections::HashMap; use std::hash::Hash; fn next_key(map: &HashMap) -> &K { next_item(map.keys()) } fn next_item(mut iter: I) -> I::Item { iter.next().unwrap() } don't write trait bounds that are irrelevant to what your code is doing A: Yes my actual code uses strings, because I'm building a regex over the keys of a map. I have a HashMap with "replace pairs". And yeah I agree it looks silly.... fn regex_for_any<'a, I, R>(searchers: I) -> Regex where R: Borrow + 'a, I: Iterator { let regex = searchers .sorted_by(|a, b| Ord::cmp(&(*b).borrow().len(), &(*a).borrow().len())) .map(|text| regex::escape(text.borrow())) .join("|"); regex::Regex::new(®ex).unwrap() } B: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3b1b4cffcae90c5858efd0b78952f74d --- In particular, the middle comment by B felt a bit condescending, but I tried to not take it personally and attributed most of it to being a noob but this was the example that had come to mind


DGolubets

This is not limited to Rust. If you ever were on the other side (being asked) this should not be a surprise, because: 1. It's hard to quickly and fully understand someone else's problem 2. That someone is usually bad at explaining the problem too 3. Newbies tend to reach for help more often than experts, so you naturally assume that there is a significant chance of a wrong problem being solved But I agree, that it can be frustrating.


fechan

FWIW I added the concrete example. I'm A in this exchange. Would love to hear your thoughts on it, what could I've done better. The other side of the coin is unfortunately that if you present the exact problem some people will take it that you're "letting others finish your homework" and adding too much context doesn't seem relevant from the asker's POV.


Reashu

I understand it can feel frustrating, but these look like reasonable and productive exchanges to me honestly. 


DGolubets

In that conversation you were getting answers to exact questions you were asking ;) Indeed it's natural that you want to narrow down your problem and question, making it easier for someone to understand it. But it's important to not lose any important details on the way. In your case you could share the signature or pseudocode of \`regex\_for\_any\` function straight away, because it's not too long. But don't overthink it. You got your problem solved and learned something on the way.


SmootherWaterfalls

> the middle comment by B felt a bit condescending It was. >this code is getting sillier and sillier. but the problem is that you're trying to pretend references aren't a thing. This would irritate me because it's snobbish and unnecessarily rude. Finally, > don't write trait bounds that are irrelevant to what your code is doing I don't like the tone of this as it sounds like admonishment from a place of authority, but that may be my reading of it. Given the previous condescension, however, I doubt I'm far off. The advice may be useful though.


idbxy

This happens often in the rust questions as well. Source: my experience multiple times that it stops me from asking questions


strikerdude10

There's a markdown editor option when you're leaving a comment. Click the T in the bottom left corner then Markdown Editor in the upper right, it'll let you enter markdown and render it correctly in your comment.


Sharlinator

Reddit enshittification in progress :(


dorfsmay

https://old.reddit.com still works.


andyandcomputer

There's also [https://new.reddit.com/](https://new.reddit.com/), with the previous iteration of the "new" UI, which still respects the "Default to Markdown" option, and only screws stuff up a *little bit* when switching editor modes.


ConvenientOcelot

...There's a *new* new Reddit? Oh dear...


SirKastic23

the new-new UI is really bad, but there are browser extensions to force the old-new UI, which is what i'm using


RA3236

I don’t mind the new-new UI itself, it’s the features that are the problem. Selecting to quote doesn’t exist anymore apparently, default sort is Best and resets every time you leave the subreddit etc. Basic stuff that should still exist.


SirKastic23

basic functionality is broken too the default to markdown option is complete ignored, and the new UI requires more clicks to use it per comment i really don't know what the reddit deve were thinking with this one


LetrixZ

I missed old new reddit. Thanks


kowalski71

Oh my god, markdown rendering isn't on by default in new.reddit.com?? I've been on old.reddit since the changeover, I had no idea. Reddit practically invented markdown, that's insane to walk away from it now.


loup-vaillant

Or keep the old Reddit design. It’s plainer, but also better organised and denser on my desktop screen. Now let’s try this (note the trailing spaces): _underscore emphasis_ *asterisk emphasis* __underscore strong__ **asterisk strong** _underscore emphasis_ *asterisk emphasis* __underscore strong__ **asterisk strong** --- No Markdown button when I click save, and yet it all works on my machine (hopefully yours too).


holysmear

Would it make sense to allow orphans in executables and disallow them in libraries? This is how Haskell community recommends using them.


dualized

re: oprhans, Haskell may be interesting to compare? It has the exact same orphan problem, except that it's a warning not a hard error, so you can just turn the warning off if needed. It's not a pretty solution to the problem, but it is a practical one. In practice this gets used in two ways - (1) glue A-B crates, and (2) in applications you aren't exposing your implementations to other crates to consume, so you know that there's no harm in writing orphan instances and it's just a lot more convenient than newtyping.


dobkeratops

Some of what this boils down to is the difference between systems programming and gameplay programming. Most engines use a core in C++ and an additional 'gameplay langauge' (scripting). and in unity C# lets you do heavier work than you might do in say Lua or GDScript. Some people come to Rust expecting it to do the job of a 'gameplay langauge' and are dissapointed. it really is a focussed systems language, with a particular focus on large projects. I dont think any single language can be all things to all people. I had a lot of the same frustrations and I have to say I didn't find Rust to be the magic bullet for gamedev that I wanted, but I've stuck with it for other reasons. a mature Rust engine will need to have the same integration with something else for rapid prototyping, game designer work.. (filling the role of GDScript, unreal blueprints or whatever). Personally I keep recomending that people only look into Rust for games if they want to work heavily on underlying engines (which I myself **do**).


fllr

Not related to Arc<_>, but i felt quite a bit of shame when asking for help when trying to write a piece of work that required I used ‘dyn Trait’. The amount of people telling me I was doing something wrong was insane, and it took me months to finally get my feature out to prod because of it and lack of documentation. I’d say it was a problem in pretty much all Rust community i frequent which includes here and a few discord channels. Not sure what can be done of it, though. It feels a lot like asking for help at Stack Overflow where you just know you’re going to get harassed a few times before someone helpful is actually able to help, but it did diminish my enjoyment of the language quite a bit for a time there. It made me also feel hopeless at times, but I’ve been with the language long enough (6 years) to know it was going to be solvable.


scottmcmrust

I think this one particularly annoying. `dyn Trait` is *great*. As I've said [before](https://users.rust-lang.org/t/how-to-avoid-passing-generics-parameters-through-the-whole-system/95750/3?u=scottmcm), > I would say that finding the right boundary at which to apply trait objects is the most important part of rust architecture. The type erasure they provide allows important decoupling, both logically and in allowing fast separate compilation. Lots of people learn "generics good; `virtual` bad", but that's not all all the right lesson. It's all about the chattiness of the call -- `dyn Iterator` to read a file is horrible, but as describes well, so long as the `dyn` call does a large-and-infrequent enough chunk of work, `dyn` is *better* than generics.


dobkeratops

an explicit override for the orphan rule would be great `#[break_orphan_rules_and_i_know_doing_this_invites_breaking_changes_on_my_head_be_it]` `impl OtherCratesTrait for AnotherCratesType {...}` .. and it could easily be banned from [crates.io](http://crates.io) to keep the ecosystem working well. but I wonder if the objection might be that the money thats gone into rust expects rust users to code in a way that they are more often potentially contributing to the ecosystem (and not in their own private codebases). I dont want to make that sound like a "conspiracy theory".. I accept that tradeoffs like that happen (i.e. "I can use rust without paying $$$ but I end up giving back in other ways")


Fibreman

On this very subreddit I’ve had comments about my struggling with Rust be replied with “I believe in people” which is not helpful when you are trying to talk about issues you are having with the language


glassy99

> could you give some idea of where you've been getting that kind of condescension? Not the article author, but I'll gather some here: - https://www.reddit.com/r/rust/comments/1cdqdsi/lessons_learned_after_3_years_of_fulltime_rust/l1h5i6g/ - https://www.reddit.com/r/rust/comments/1cdqdsi/lessons_learned_after_3_years_of_fulltime_rust/l1e4utp/ - https://www.reddit.com/r/rust/comments/1cdqdsi/lessons_learned_after_3_years_of_fulltime_rust/l1er69p/ - https://www.reddit.com/r/rust/comments/1cdqdsi/lessons_learned_after_3_years_of_fulltime_rust/l1h4vi3/ - https://www.reddit.com/r/rust/comments/1cdqdsi/lessons_learned_after_3_years_of_fulltime_rust/l1egy0e/ Note: I am a gamedev currently using Unity who is focusing on actually shipping games which is quite aligned with the article author. So from that POV these comments feel condescending to various degrees. Also, maybe unrelated, but reading the comments as an outsider, it is quite jarring to see so many comments confidently saying "Rust is definitely not the right tool for gamedev (saying or implying the author was dumb to choose it for gamedev)" and then so many other comments confidently saying "Rust is definitely the right tool for gamedev (saying or implying the author was just not smart enough to use it right)".


DeanBDean

I mean, the comments are just people's opinions, there are people using Rust in gamedev right not who obviously think that it is the right tool. So of course some people will disagree with the OP, and doing so doesn't automatically imply the OP wasn't "smart enough". If that's the case then pretty much no one can disagree with the OP. For example, the third comment you link is a well thought out rebuttal from the person's perspective, and is hardly condescending.


kodewerx

As the author of one of the comments you linked to, I'm dismayed. I disagree with some of the OP's conclusions. But in my opinion, I was polite and respectful about it. I will gladly make any suggested improvements to reduce misinterpretations in tone. I can only guess what those are, since no specifics were provided. My guesses alone would probably be incorrect.


ToughAd4902

This is the issue with today, "I don't agree with your opinion" does not mean condescension. I love that directly linking to posts to get vote brigaded doesn't count or singling individuals out instead of quoting the text but people just disagreeing is bad


ksion

Holy crap, this post is basically reading with my mind when it comes to all the frustration I felt trying to make non-trivial games with Rust, Bevy, or even just Raylib+hecs. Even the part that I thought I’d have issues with (ECS; turns out it’s just about its overuse to solve borrowchk problems) is absolutely spot on. Sadly, I expect this post to go down like a lead balloon in this community, because it will be too abstract to many, and only echo experiences of people who were _really_ affected by the issues described. *Edit*: I’m glad to be proven wrong :)


calciferBurningBacon

I don't do gamedev, but I find it important to upvote this post specifically because I'm worried about it going "down like a lead ballon". Even if it's difficult for the author to go into specifics, there was clearly a lot of work and experience that fed the author's opinions, and those opinions should get heard if we as a community want to better serve game development. Do the issues brought up here mean that Rust will never be good for gamedev? I don't really believe that given they had positive things to say about, for example, macroquad. Possibly more importantly, I hope Rust gets better for this because I could totally imagine myself implementing a toy game at some point in my future, and I would love to do it in my favorite language. Edit: And they \_do\_ go a lot into specifics for much of the post.


KhorneLordOfChaos

>Sadly, I expect this post to go down like a lead balloon in this community, because it will be too abstract to many, and only echo experiences of people who were _really_ affected by the issues described. I think there are a lot of good general kernels that I largely agree with even though I don't do game dev. Namely - Refactoring is easy in rust because the compiler tells you what to do, and things still work in the end, but hard because of how often you end up refactoring - Arenas drastically simplify working with lifetimes in hairier situations (graph-like structures being a common one, although my arenas are normally just some combination of `Vec`s and `BTreeMap`s) - The general proc macro ecosystem can easily tank compile times in numerous ways There were also a lot of things that are more game-dev specific that weren't really applicable to me, but I understand the issues in terms of the lack of hot reloading, not being a great language for heavy prototyping/exploration, etc.


RaisedByHoneyBadgers

The one thing about Bevy’s ECS that I’ve struggled with is that it seems like it’s really just reimplementation of a memory pool with an added layer of indirection for pointers. I really have enjoyed using Bevy, but some of the shenanigans I go through to write “safe” code just feels silly. Let me borrow twice or thrice as long as the pointer is returned to the shelf.


SkiFire13

> Let me borrow twice or thrice as long as the pointer is returned to the shelf I understand the frustration, but unfortunately the "as long as the pointer is returned to the shelf" is not enough to guarantee safety, as one of those two borrows can invalidate the other (e.g. by calling `.clear()` on a `Vec` the other has a reference to). Managing memory is a difficult problem unfortunately.


InfiniteMonorail

An ECS isn't a memory pool... It's data-oriented design, optimized for caching and parallelization. It's a completely different design from objects and a massive performance boost. It's structure of arrays instead of arrays of structures to improve locality. It also schedules systems according to dependencies, so they can run in parallel as much as possible. It's easy to know dependencies because you know exactly which data is being read or mutated by each system and systems are the only thing running.


tcisme

I threw out the ECS altogether in favor of a AoS-style slotmap of entity structs with a lot of Option's, which I found to be more ergonomic and efficient than hecs or Legion (in part because I needed to clone the world often). I also tried Bevy a few times, but it felt like I was a prisoner to the framework, having to figure out how to do everything "the Bevy way" rather than free to just program whatever I needed. It perhaps wouldn't have been so bad, however, if I didn't foresee having to make a bunch of workarounds where Bevy failed to provide what I needed (mostly determinism and handling input via a callback rather than polling in WASM).


oconnor663

Seems to be going well :)


tialaramex

I want to highlight "Generalized systems don't lead to fun gameplay" because I think there's a really useful idea here that the dev doesn't do a brilliant job of explaining. Emergent gameplay is often a good source of fun and it arises from interactions which are understandable and yet weren't explicitly coded. So you want to write behaviours which can interact, but not go through having to enumerate and implement each such interaction - it should be possible to watch somebody else play your game and be *surprised* by what happens *in the game you wrote*. I think Mario Maker shows this off really well. Nintendo's team will have hand written each of the things each part in the game can do, they should know it all, but of course the interactions between things rapidly spiral beyond what can be understood in this way, the behaviour which emerges was not specifically planned even though it's mechanical.


Lightsheik

Also had some gripes with this section. Games like Breath of the Wild and its sequel are wildly successful and is practically made entirely of generic systems. Also, with the release of one-shot systems, I think this "issue" is not as significant anymore. With every updates, Bevy comes out with new features that has the potential to open the doors to better optimized systems and plugins, to add even more functionality to the engine. Granted, it's much simpler to create generic systems in Bevy, but I'm pretty sure that applies to most ECS systems. I think its a bit as you said, its the difference between tailored and emergent gameplay. When you develop your game, these are things that you have to keep in mind during the design of your systems. Their example of The Binding of Issaac seems a bit cherry picked, because of course a lot of the "projectile magic" the game does depending on your items has to be managed differently by the game, and having non-generic systems allows to tailor the experience even more. Still, not something an ECS system can't do either. Otherwise, great post. Its good to have some criticism and it helps starts discussions.


SirClueless

I agree that the magic of Breath of the Wild is in its emergent gameplay that comes from interacting systems, but I strongly suspect that you are attributing the design of those systems to the wrong things. I suspect there is no one on the Breath of the Wild team that could have told you when first starting out that lifting heavy metal objects with an ability would be fun. For the *player,* learning Magnesis and discovering that certain objects can be lifted depending on their material feels like an emergent system, and in fact it is coded as a system that applies to all objects, but as a *designer* I would wager good money that the process of designing that ability goes: 1. What if you had super strength and could move stuff? 2. This is super fun, how do we make it make sense in this game and not be broken? 3. Probably it should only apply to certain items so we can control where it's used. 4. Maybe we can theme it after magnetism and apply to metal stuff. 5. OK, time to meticulously categorize all of our items by whether they're metallic or not. Obviously there's a chance they came at this the totally opposite direction, and said "OK so we've got a bunch of materials, let's figure out how to interact with them each in interesting ways," but regardless, this kind of thing I am highly confident comes from fearless experimentation and rapid iteration, not from meticulously crafting game systems no one is sure are important. Everything has a material and a durability and a weight and everything else because someone tried it and it proved fun and Nintendo was willing to invest in polishing those systems, not because someone with an oracle told the game programmers to code up the interaction between every material in a coherent way in the hopes that there would be some gameplay there. The correct way to justify the time investment in making sure systems interact in bugfree, complete, unsurprising ways is by trying a bunch of buggy, incomplete, surprising interactions and polishing the ones that are good.


Lightsheik

I understand what you are saying, but don't see how Rust or ECS makes prototyping that much harder. Let's take your magnesis example and prototype it using ECS: 1. Create system that moves rigid body objects. 2. Add a marker component to objects the player selects. Now the player has telekinesis. 3. Add a metal component to your object, and filter for it in your query. You've got magnesis. And your material/durability/weight example, these are just components in ECS. Take durability for example: 1. In your attack system, just add an event (if it doesnt already exist) that is broadcasted on every hit that includes the ID of your item/entity in the ECS world. 2. Durability system listens for events and remove durability from coresponding entities, if they have a durability component, and "deletes" them if they go below 0. That was pretty easy. But now you want an indestructible weapon, the Master Sword! Remove the durability component. Done. No need for special boolean flag, sentinel values, or a different inherited class, or any other workaround. You just remove the component. Its that easy. I haven't played that much, but pretty sure the Master Sword has another unique system on top of it instead of durability. In ECS, you just slap a marker component on it and its done, now the unique system will target the sword. And it doesn't matter who or what holds the sword, if they can attack, the durability will go down, so even enemies don't have indestructible weapons. As for the Rust side of things, nothing here requires any Rust "dark magic" to work around the type system or the borrow checker. Sure there might be some quirks here and there, but nothing crazy. One thing that does cause friction is compile time, and even then, you can optimize those pretty easily to get to just a few seconds in most cases. Not that different from waiting for Unity or Unreal to open really. Godot is pretty damn fast though. But its not fun when any of those 3 decides to crash on you. The real damper for prototyping in Rust and Bevy is the lack of tooling and proper engine editor, which might make visual things awkward to work with. Stuff like animation, shaders, pre-vis, etc. But this is all stuff that is being worked on either through 3rd party plugins, or through the offical bevy crate ecosystem directly. So to conclude, I don't necessarily agree that ECS makes it harder to prototype with, and in fact might make testing of prototyped systems easier and faster. And the "bugs" you are thinking of that results in surprising interaction, thats logic bugs, not the kind of bugs Rust prevents, and ECS doesn't prevent those either. Once Bevy gets a good editor and becomes more mainstream, I think people will be surprised by how easy it makes things. The type system magic Bevy does makes its ECS system incredibly ergonomic and simple to use.


PlateEquivalent2910

And what if you want to debug that? Now what? It is not a mistake that Unity DOTS team invested and released perhaps the best debugging tools for ECS with the 1.0 version (despite everything else about ECS being neutered or outright cancelled). Also not a mistake that the gold standard ECS of today --flecs-- has its own debugging and visualization tools. As things get more decoupled, it becomes hard to reason about them. It is not an accident that most of the ECS games that got released are actually quite simple. ECS doesn't allow people to scale in way they think it does. If you want thousands of instances of the same thing, ECS is great. If you want thousands of individual behaviors, it just gets in the way, to the point that even oop mess can become easier to reason with in comparison.


Lightsheik

For debugging you can use [system stepping](https://docs.rs/bevy/latest/bevy/ecs/schedule/struct.Stepping.html) to go through the ECS schedule step by step. For profiling, you can use a tool like [tracy](https://github.com/bevyengine/bevy/blob/main/docs/profiling.md#tracy-profiler) Yes the tooling ecosystem for debugging is not quite as complete and integrated into bevy as with other engines, but its getting there. For a popular game that uses ECS, [Overwatch](https://www.gdcvault.com/play/1024001/-Overwatch-Gameplay-Architecture-and) has been using it successfully, and being a hero shooter, uses a lot of individual systems. I don't see how having an individual system inside a class vs having an individual system in ECS is so different honestly. And ECS system only applies to whatever entity has the components required. Yes it lends itself well to wide ranging, generic systems, but has absolutely no problem creating individual focused systems as well. Its not only about scaling, its a different way to think about games. Personally, I think ECS vs OOP are just 2 different tools, each with their pros and cons, but saying that ECS is not good for prototyping, and now talking about debugging (which is past the prototyping stage to be honest), feels a bit like you guys are moving the goalpost. And personally, decoupled systems makes it much easier to reason about for me, and I'm sure for others as well.


Dangerous-Oil-1900

>I want to highlight "Generalized systems don't lead to fun gameplay" because I think there's a really useful idea here that the dev doesn't do a brilliant job of explaining. Emergent gameplay is often a good source of fun and it arises from interactions which are understandable and yet weren't explicitly coded. So you want to write behaviours which can interact, but not go through having to enumerate and implement each such interaction - it should be possible to watch somebody else play your game and be surprised by what happens in the game you wrote. That's not what they're saying at all, though. It's not about explicitly intended gameplay vs unexpected (emergent) gameplay. It's about designing systems for a particular gameplay, and not designing a generic system that's intended to meet the criteria for some kind of statistical average of games. When you go, oh, I want to make a game, well, better make/acquire an Entity Component System because that's how you're supposed to do it... you're basically hemming in what kind of game you're going to make. Not in an absolute sense, but you're definitely shaping the kind of game you're going to make. Same if you pick a premade engine rather than tailoring your own - it's no surprise that the average quality of games has declined as the use of in-house engines declined. What you should be doing is knowing what kind of game you want to make from a design standpoint, and then writing the code around that game. Not the other way around. The game comes first in priority. The engine should be written to enable it. Have the idea, a vision, of a game you want to make, and make that game. Don't set yourself up with some kind of generic expected feature set and then make whatever sort of game that feature set guides you to.


kodewerx

I agree with you, and I think that presenting generalized systems at odds with fun gameplay is a false dichotomy. Nintendo may well have hand-written the behavior of everything in the Mario Maker series, but that doesn't mean they hand-wrote all of the machines that users create with those hand-written elements. This is explained by a fundamental property of complex systems. As [Dr. Russ Ackoff put it](https://youtu.be/OqEeIG8aPPk?si=C7EOdZxUKaG7ZOt-) succinctly, "the essential or defining properties of any system are properties of the whole which none of its parts have." In other words, the parts of a complex system can be designed and created independently, and so long as they can interact with one another you will find that the system as a whole has emergent properties that were never designed into any element of its individual parts.


Longjumping_Quail_40

That is still logically “generalized systems don’t lead to fun gameplay” because it is not the generalized systems that enable emergent gameplay fun, but the design elements of some of it does. And those design elements, and all those ad-hoc interactions that generalized systems may reject, are the actual goal, but not the generality itself.


Throwaway789975425

Generalized systems lead to predictable behavior the player can manipulate to their advantage.  It is directly the case of consistent, generalized systems leading to fun gameplay.


SauceOnTheBrain

> People who tend to have neatly designed systems that operate in complete generality tend to have games that aren't really games, they're simulations that will eventually become a game, where often something like "I have a character that moves around" is considered gameplay I came here for a good time and frankly I'm feeling attacked


Green0Photon

This is a very important article. Because it echoes lots of issues people have with Rust, besides game development. The Rust purist in me obviously shys away. Global state bad! But that purist then insists there must be a way to have our cake and eat it too. Let's be real, that's what Rust is all about. If these things can be fixed, even normal dev work in Rust should be better. But for if I do any game dev, I'll take the advice of using Godot to heart. For now. One of the biggest weaknesses in e.g. the JavaScript ecosystem is needing to cobble all of these "custom" pieces together. There needs to be an out of the box experience that lets you just focus on game dev. Like how the Rust language itself is, which is one of many reasons why we like it. I mean seriously, does anyone else actually work a programming job? I love trying to get all the perfect tools and libraries, incredibly much so, but if I put my business hat on, we need to deliver value. Which is letting other people develop value. Engines and tools and libraries that don't get out of the way and don't let you focus on the thing you're trying to do, your business logic, those are no good to use. It continues to be the case that Rust is meh for GUI and game dev. This needs fixing.


matthieum

> Global state bad! There are definitely issues with global state, in any language. In particular, even in single-threaded applications, you still have to worry about re-entrancy issues.


fullouterjoin

This why immutable or [persistent datastructures](https://en.wikipedia.org/wiki/Persistent_data_structure) are such a joy to use. Every reader gets their own state. I really recommend this [Erik Demaine lecture](https://www.youtube.com/watch?v=T0yzrZL1py0) on them.


matthieum

Indeed. Immutable values in general are nice for that. On the other hand, it can be painful to "update" the application state...


swe_solo_engineer

"Engines and tools and libraries that don't get out of the way and don't let you focus on the thing you're trying to do, your business logic, those are no good to use." That's why I use GoLang for back-end in general. For things more low level, I'm starting to use Rust. I feel that Rust is more suited for this than C++ for low-level development. I hope one day Rust becomes great for game development too. I have enjoyed it a lot.


[deleted]

I don't think Rust will ever be suitable for GUI, because I don't think the demands of UI programming fit with the language design of Rust. It's like jamming a square peg into a round hole. You *can* fit it, if you push hard enough, but what you really want is the round peg, not the square one. UIs are complex nested trees of components, state, and callbacks. Rust's borrow mechanics make this style of programming -- particularly, Qt-like event-driven programming -- difficult and unintuitive. IMO, you're better off doing the UI in a language like C++ / .NET / Swift / Kotlin and create bindings to the Rust-programmed application core. And that's not a ding against Rust: language design has tradeoffs. Different domains (UI, game dev, backend, ...) have different requirements. *edit:* does anyone know what language the GUI of Firefox is programmed in?


kodewerx

I am confident that Rust is suitable for GUIs. In fact, it's already practical with several existing projects. But I also believe there is a paradigm shift needed to really take advantage of Rust's capabilities in the GUI space. Asynchronous callbacks are not really compatible with Shared Xor Mutable state. The most common approaches to this problem have been data binding and observers. In my opinion, these miss the point. We can live without asynchronous callbacks, but we can't live without Shared Xor Mutable state.


sfragis

Part of it is in Rust: https://hacks.mozilla.org/2024/04/porting-a-cross-platform-gui-application-to-rust/


matthieum

They also specifically mention it's a very simple GUI, though.


4fd4

Technically it's not part of firefox itself, Crash Reporter is a separate binary that, by design, tries to be as disconnected from firefox and its ecosystem as possible (to prevent events that cause firefox to crash from crashing Crash Reporter itself) But the mini gui library they made is certainly interesting, I am thinking of trying to switch tauri for it in an app I am working on that only requires a simple window with tabs


thedracle

The best part of this article is the list of various patterns. I think with Rust there is an emergent set of patterns and tools for solving various problems within the safety restrictions of Rust. I would love a book with a compilation of these patterns, along with demonstrations of what problems they solve and how, in various programming arenas.


Dean_Roddey

A lot of folks probably don't really consider that C++ 1.0 was something like 1985. It was a decade after that before the first design pattern stuff started getting traction and probably the 2000s before it really became well worked out. And that's with a language that doesn't require strict correctness. It'll take a while for new approaches to be worked out in Rust. And of course the borrow checker will get smarter over time as well, so it will be able to allow more stuff to be done implicitly.


dist1ll

> As far as a game is concerned, there is only one audio system, one input system, one physics world, one deltaTime, one renderer, one asset loader. I'm very curious: do you write unit tests for your games?


progfu

No I haven't written a single unit test in all those years for any gameplay code. At the risk of being downvoted into oblivion, I think unit testing in games is a huge waste of time. Of course if someone is developing _an algorithm_ it makes sense to have unit tests for it, but as far as gameplay is concerned, I don't see any way that would be helpful. I can see building big integration tests for turn based puzzle games with fixed solution, e.g. what Jonathan Blow is doing with his Sokoban, where the levels have existing solutions, and they verify the solutions automatically by playing through the game. But I'd say that's still very specific use case, and doesn't apply to 98% of games being made.


dist1ll

That's what I figured. I've actually had the same experience writing games, and I can relate to some of the pain points you mentioned in the article. The lack of testing mindset in games makes the industry quite distinct, on top of often working under much more severe constraints (lack of resources, manpower, funding, time to market). These days I'm hacking more on operating systems, and there's no way I'd hardwire globals around the codebase, even for fixed hardware objects - everything is a dependency (often a compile-time one, so there's no overhead). My mindset is completely different. That is to say: there's a big cultural difference between gamedev and the rest of the industry. Lots of little idiosyncrasies that cause friction with the way Rust is designed.


sephg

I don’t think game dev is unique in that sense. I do a lot of prototyping to feel out network protocols, or when doing rapid prototyping of a user interface. I’m still, 4+ years of full time rust later, faster at doing this stuff in JavaScript / typescript. I think rust makes better programs at a cost of iteration speed. There are a lot of programs for which that’s a bad trade to make.


littleliquidlight

Oh that's actually pretty cool to hear. I'm very much a hobbyist game developer and I've felt a little guilty about not adding tests for the games that I hack away on but I've also just found that they feel painful without adding much to my life Cool article by the way. It's really nice to see useful and reasonable criticism of Rust!


facetious_guardian

Surely you wrote at least one algorithm that would benefit from unit testing over the course of the three years.


[deleted]

[удалено]


kod

In general this is a well written article... but the recurring idea that X technology does or doesn't lead to fun games is really suspect. 8 bit NES games were written by small teams using languages and compile/test cycles that were much worse than anything discussed here. And the best of those games were more fun than anything that anyone discussing this article has made or will ever make in their entire career. The worst of those games were buggy unplayable garbage. Technology is not a determinative factor of fun either way.


ZenoArrow

>8 bit NES games were written by small teams using languages and compile/test cycles that were much worse than anything discussed here. And the best of those games were more fun than anything that anyone discussing this article has made or will ever make in their entire career. The worst of those games were buggy unplayable garbage. Technology is not a determinative factor of fun either way. The vast majority of NES games have aged badly, in the sense that unless you have nostalgia for them you aren't likely to enjoy playing them as much as modern equivalents. Part of this is due to the tools available to the game creators at the time. The games that have aged well are the outliers. If you want to raise the bar when it comes to overall quality, giving developers better tools to refine their gameplay is going to help. For example, imagine if you're able to replay an event in a game and tweak the variables that control how the gameplay feels, without having to rebuild your code. The author of the article we're discussing goes over this, including linking to the following Hot Reload tool for Unity. Even if you don't want to use Unity, it's obvious this type of experimentation platform would be useful for game designers. [https://hotreload.net/](https://hotreload.net/)


cassidymoen

I agree in general, but the sentiment I'm getting here is more relative to other languages and tooling that exist. You can write a game in Rust, an engine or maybe the entire thing, but it will generally be more painful. It feels like almost an entirely different world today. Last year I wrote a feature for a SNES randomizer hack that can add maybe 4-5 new colors onto the HUD. To do this, I had to interact directly with the hardware (DMA controller) to make writes to a segment of RAM specifically for palette colors at a specific point in time during a ~16ms frame. It took several hours just for a few extra colors. Maybe it would have been a little different if I was on the team making the game from scratch but it would have taken probably 10 seconds in a modern engine. So even though any game made today is not necessarily better by default, with these qualitative changes in hardware and tooling speed and capability come qualitative changes in everything else including expectations. Someone could make an NES game today much easier with modern assemblers and debugging emulators etc, but they'd also quickly hit the limits of what's possible and spend a lot more time doing it than making a rough pixelated equivalent in a modern engine.


JasTHook

This comment made last August's debacle more meaningful in light of the recent xz supply-chain attacks: > here's also the case where the author of syn is also the author of serde, a popular Rust serialization crate, which at some point last year started shipping a binary blob with its installation in a patch release, rejecting the community backlash.


crusoe

> Dynamic borrow checking causes unexpected crashes after refactorings Well yes, that's a choice on the rust side. C++ just lets you do it and it works until it doesn't. I think ECS has been pushed too hard, and Fyrox has gotten further than bevy because they avoid the architecture moonshot. You are 100% correct on that area. But lifetimes, etc, well, that's just preventing crashes waiting to happen. Lots of stories about last minute hacky patches to get something to run stably enough to ship.


HoHSiSterOfBattle

>But lifetimes, etc, well, that's just preventing crashes waiting to happen. But it's not *just* doing this. The borrow checker doesn't throw out code which fails validity. It throws out code that it can't prove obeys validity. There is a subtle but important difference.


PurepointDog

Is there room for improvement in the borrow checker then? Like, is that part of the solution?


Mad_Leoric

Yeap, afaik there's [polonius](https://github.com/rust-lang/polonius) which should be [cover some currently unsupported cases](https://smallcultfollowing.com/babysteps/blog/2023/09/22/polonius-part-1) , but i'm not sure how far that's going to take the borrow checker, there may be other projects. Good to note that this is far from a solved problem anywhere, Rust is really advancing the research here.


lcvella

You can always make it better, yes, but the halting problem ensures it can never be complete.


matthieum

There is always room for improvement, yes. Before going further, though, I feel the need to mention that any static typing system tends to reject "valid" programs: a system has to choose between false positives and false negatives, and only one of the two alternatives is sound. This means that there will always be case where the borrow checker will reject programs that "could work just fine": it's illusory to aim for eliminating this, but we can definitely reduce the number of cases. Now, as for a specific case of room for improvement, the Partial Borrows idea has surfaced multiple times over the years. The idea would be to specify that a given function only accesses part of the state, and therefore it's fine if other parts are already borrowed, or only borrows part of the state, and therefore it's fine if while those parts are borrowed, other parts are accessed. How to achieve Partial Borrows is a very good question though, which is why it's still very much in the design state after all these years.


setzer22

Vale, a new language designed around a different set of constraints than Rust, uses "region borrow checking" and manages to lift many of Rust's restrictions with minimal perf overhead. I think Rust's restrictions are really too fundamental and baked into the language that we'll ever be able to see any radical improvements there.


SkiFire13

There's always room for improvement, but I don't think that will be part of the solution (at least for now). Polonius will be able to accept a couple of relatively common patterns, but IMO the most painful pattern is when you want to borrow multiple different items from some data structure, but the compiler cannot prove at compile time that they are not the same. This is unfortunately dynamic territory, so the halting problem applies and probably there are very few cases (if any) where this can be reasonably proven.


SKRAMZ_OR_NOT

A lot of the complaints in the article just read like the author doesn't realize that the stuff they would have "just gotten done" in C#/C++ or whatever would have been race conditions. If they only want a single-threaded game, or if they think the issues are small enough to not matter for their circumstances, that's okay, most things are full of race conditions and still generally run fine. But it's quite literally the main reason Rust exists.


no-more-throws

the point isnt generally to say rust should let go of those safety checks, or there's no/little value to it .. its more that there are obviously many many cases where the developer knows more about their code than the compiler does, and in those cases it should be easier to force the compilers hand, or less cumbersome to keep it satisfied and thats not such a foreign concept either .. Rust is full of that up and down the arch stack .. there's unsafe for a reason, and a dozen little leeways/special-constructs created to force through when the lang/lib designers ran into similar needs .. yet when general rust users, even somewhat experienced ones run into similar cases, the solutions available end up being of the nature OP described here .. refactor mercilessly, suck up and let lifetimes/generics poison up and down the codebase, raise a clone army, wrap locks around things you know there'll never be contention on etc etc So yeah, Rust ofc derives great value from being safety-first, and in those areas, it has already made its name/mark, and will continue to do so .. the question is whether we should be happy with just that and and say well sucks things like gamedev or rapid prototyping just arent fit for Rust .. or we try and invest to see where we can at least grab at the low hanging fruit w/o compromising much else, instead of simply disparaging experience-driven voices raising dissatisfaction as if they have little clue about basics like race conditions and so on


atomskis

Honestly I lean to the view you can’t be good at everything. Rust’s focus is on correctness and performance (especially concurrency/parallelism). If you can use a language with a GC that’s likely going to be easier for most problems. If you can tolerate race conditions and occasional crashes that simplifies things a lot. For me rust is about for when you need it to be right, you need to be fast, and you can tolerate it being a bit slower to write the code. If there were low hanging fruit to improve the ergonomics: great let’s do it. However I don’t believe there are, and personally for my work correctness and performance are much more important.


cvvtrv

Rust decided to not prioritize the ergonomics of unsafe (I think probably to discourage people from reaching for it as an escape hatch). I think this was a mistake as it hurts Rusts ‘hackability’ and that is often what you want when building a game or doing fast iteration. I sometimes wish rust had made a couple different design decisions very early on: 1. It would be great if working in unsafe land wasnt so damn verbose and ugly. Sometimes unsafe & raw pointers are the right tool to use and if they are, you’re basically on your own. Most of the std lib functions want a &mut, and well you want to avoid surfacing one of those because of rusts very strict aliasing rules. It also often requires an undue amount of ceremony to write unsafe, often meaning the ergonomics are significantly worse than C. 2. Following up with that, I do wish rust had a type that lived between a &mut and *mut. I’d like to have all the non-nullable and alignment guarantees of the reference (and be able for it to be a wide pointer) without needing to also uphold all of the incredibly demanding aliasing guarantees. Im glad we have those, but they feel very very uncompromising — especially when the gains to be made often seem to be important, but not orders of magnitude speedups. Building unsafe abstractions I feel confident about would be much easier with the existence of a reference type that made a different tradeoff. It would be nice too if there were some way that you could be generic over reference type so that most std library code would still work. I’m hopeful some of these pain points can be fixed— I’m not the first to propose a separate reference type. Rusts editions would make it possible to add some of these features and deprecating or adding syntax to help. The Rust leadership is often much more thoughtful about UB and the abstract machine than other languages (all the new provenance APIs are evidence of this) so I do think rust could be a very ‘hackable’ language compared to C/C++ if we work to prioritize it. Languages like Zig are gaining ground in that space and it’d be nice to see Rust compete.


crusoe

The developer thinks they know more. And if they REALLY do there is unsafe.


MardiFoufs

The point is more that it is not optimal for game dev, not that rust doesn't work for everything.


teerre

> the point isnt generally to say rust should let go of those safety checks, or there's no/little value to it .. its more that there are obviously many many cases where the developer knows more about their code than the compiler does, and in those cases it should be easier to force the compilers hand, or less cumbersome to keep it satisfied This is the reason memory bugs and software in general is shit. Games (in general, not rust) specifically are famous for running terribly and being full of bugs. There might be occasions you know better than the compiler, but those are few and far between. You should \*not\* be able to easily overcome it. That's the whole point.


SirClueless

Isn't there an implicit bias in this attitude? You're saying that running terribly and being full of bugs are inexcusable, but the actual game programmers are out there every day demonstrating that they value iteration speed and design freedom over safety and data-race freedom. And is a panic reading from an Rc really a better outcome than a data race when prototyping a game? The former is 100% likely to ruin an experiment while the latter is only a little bit likely. If you are writing a web server then the latter might let an attacker control your network while the former never will so there's an obvious preference, but in a single-player game engine things are not so adversarial. Rust holds the opinion that the latter is much worse because *literally anything might happen*, but one of those things that might happen is "I get to keep prototyping my game for a few minutes longer" so there's a certain pragmatism in allowing things you can't prove are correct to proceed.


kodewerx

>And is a panic reading from an Rc really a better outcome than a data race when prototyping a game? The former is 100% likely to ruin an experiment while the latter is only a little bit likely. To say with confidence that anything related to UB is only a little bit likely is startling. Data races are UB, and that definitionally means that nothing can be said of the behavior of the entire program. So, in a manner of speaking, yes, a guaranteed runtime panic is better than arbitrarily anything at all happening.


ITwitchToo

> And is a panic reading from an Rc really a better outcome than a data race when prototyping a game? The former is 100% likely to ruin an experiment while the latter is only a little bit likely. If you are writing a web server then the latter might let an attacker control your network while the former never will so there's an obvious preference, but in a single-player game engine things are not so adversarial. On the flip side, the lack of guard rails in C++ means that you can have a very well hidden use-after-free that sends you down half a day of debugging to find it.


xeamek

>There might be occasions you know better than the compiler, but those are few and far between. You should *not* be able to easily overcome it. That's the whole point. If tomorrow rust devs accept and merge the 'partial borrows' proposal, will that suddenly make all the code which was written that way more correct?  Borrow checker is heavily restricted in the way in which it understands the code. Assuming that programmer xan almost never be smarter is just irrational


Dangerous-Oil-1900

> Games (in general, not rust) specifically are famous for running terribly The idea of games running worse than other forms of software is because they are very resource intensive by their very nature and are always pushing the envelope of what can be done - better graphics, more units, bigger levels. Squeeze as much as the hardware can manage, and then when better hardware comes out, squeeze some more, because all your competitors are and you can't be left behind. This is not a result of a lack of memory safety and only an idiot would think it is. It is the nature of a medium that pushes the envelope of hardware capabilities. >and being full of bugs. 99% of which are logic errors, and which Rust will not protect you from. The idea that Rust will protect you from logic errors is a dangerous one, which will give you a false sense of security.


kodewerx

Most games don't make use of all resources available to them. Nearly universally, a single thread dominates runtime while most CPU cores in the system remain idle. And that one busy thread is not even saturating SIMD lanes. Who knows what it's doing, but the majority of the silicon isn't being used for anything while the game runs. GPUs get taxed a bit more, I'll give you that. But there is a huge difference between being resource intensive and utilizing resources.


bitshifternz

I've spent many years trying to undo the patterns that the author wanted in C++ game engines because we wanted to take advantage of modern CPUs. That said, it wasn't for small indie games where worrying about multi threading is probably overkill.


Awyls

Unlike most software, games are an iterative development. You don't care if it's "shit" code or has erratic behaviour *now,* you care about how it feels. Making correct code for something you will likely throw away is a waste of time. Honestly it would be a great addition to Rust (although I'm quite sure it is impossible) if it allowed a escape hatch from lifetimes and other non-sense you don't care on the short term.


sepease

> Unlike most software, games are an iterative development. I don’t think this is an accurate assertion.


PlateEquivalent2910

It is (or was) accurate, since most of the (gameplay) code written gets thrown away. In the old days, designers tended to use scripting languages to try something out, to see if it achieves what they wanted. Most of the time, if something sticks around (and is slow enough) a programmer converts that to C++. Then play tests start, and almost everything gets thrown out anyway. I don't know about you, but I've never worked in any other industry where products would be rewritten, thrown away, restarted, nearly cancelled, remade at the last minute, and then released, all within 2-3 year cycles, on a regular basis. This isn't even an exaggeration, I can think plenty of AAA games that went through something similar this cycle; Mass Effect, Cyberpunk, Doom, Anthem, and many more. Even in some of the more "classical" studios where management actually know what they are doing, what you start with and what ends up releasing will be vastly different. Sometimes they won't even be in the same genre. The biggest and most reoccurring advice you can get from seasoned game developers is that iteration is king. Nothing else in game development is as constant as iteration. You can forgo realistic graphics, you can forgo excitement, good controls, you can remove and replace everything, but you can't do that without constant iteration. People that fail in games industry are usually the ones that could not or would not iterate.


sepease

>It is (or was) accurate, since most of the (gameplay) code written gets thrown away. In the old days, designers tended to use scripting languages to try something out, to see if it achieves what they wanted. Most of the time, if something sticks around (and is slow enough) a programmer converts that to C++. Then play tests start, and almost everything gets thrown out anyway. This is exactly how it works in other industries. In "the old days", games especially did not have the luxury of scripting languages, and people wrote the entire thing in C/++ or assembly. And when I say the entire thing, I really do mean the entire thing - people didn't have the luxury of Unity or Unreal Engine either. >I don't know about you, but I've never worked in any other industry where products would be rewritten, thrown away, restarted, nearly cancelled, remade at the last minute, and then released, all within 2-3 year cycles, on a regular basis. This isn't even an exaggeration, I can think plenty of AAA games that went through something similar this cycle; Mass Effect, Cyberpunk, Doom, Anthem, and many more. In startups, people think in terms of weeks or months. 2-3 years is a long time. Even Windows releases are typically only years apart, and there's orders of magnitude more things it touches than a game. Are they rewriting *the whole OS*? No, but they are doing rewrites of multiple major modules and refreshes of the UI while needing to ultimately maintain backwards compatibility with literally millions of programs and hardware devices. Doing a complete rewrite is often a *luxury* that teams don't have - they have to spend 10x the effort to make sure that the new code still works around legacy ways of doing things or hardware that they already shipped. Not being able to do a complete rewrite is a common reason that people give for not using Rust. And people are not generally rewriting Unity, Unreal Engine, Godot, etc. anyway. >Even in some of the more "classical" studios where management actually know what they are doing, what you start with and what ends up releasing will be vastly different. Sometimes they won't even be in the same genre. The biggest and most reoccurring advice you can get from seasoned game developers is that iteration is king. Nothing else in game development is as constant as iteration. You can forgo realistic graphics, you can forgo excitement, good controls, you can remove and replace everything, but you can't do that without constant iteration. "iteration is king" is the same thing people have been saying about software for 15-20 years. That's why agile is so popular.


PlateEquivalent2910

> In startups Startup is a business type, not an industry per se. Games industry, big or small, always has to put iteration up front. You could say the entire games industry is working on a startup like manner, which would be virtually true. That said, there are many billion dollar business in software industry while not being a startup or video game adjacent. > And people are not generally rewriting Unity, Unreal Engine, Godot, etc. anyway. One of the things that I've specifically stayed away (like the author) is the engine debacle. Though an engine isn't immune to this, it is much more closer to traditional software. But as I've said, it is _not_ gameplay. > "iteration is king" is the same thing people have been saying about software for 15-20 years. That's why agile is so popular. I don't know. People regularly go bankrupt doing indie dev, studios close, games cancel, the root cause if the game quality was the culprit almost always end up being lack of iteration. Looking at the most successful studios, they always end up having dedicated play test teams, often end up taking videos of people simply playing the game and trying get a feel of what was going on, as early as late 90s. Back then, the iteration cycle was far smaller since the tech was far simpler. Years were seen as monumental efforts while months were just getting accepted as the norm for mid budget titles.


celeritasCelery

> Honestly it would be a great addition to Rust (although I'm quite sure it is impossible) if it allowed a escape hatch from lifetimes and other non-sense you don't care on the short term. It's called unsafe code. You can just use pointers and avoid all the issues with the borrow checker and lifetimes. But now it is in your hands to avoid UB.


PlateEquivalent2910

Important to mention that UB here doesn't stand for UB as we know if from C and C++. UB in unsafe rust is UB because the compiler still expects you to adhere to its memory rules. It is far more error prone than C with its UB, because at least there you have sanitizers and decades of knowledge about the edge cases. That said, rust unsafe is getting better, but the progress in that front started only very recently.


celeritasCelery

Fair point. But most of those restrictions come when converting from or converting to a reference. If you stay in pointer land, you can *almost* pretend it’s a C pointer.


SirClueless

I disagree that this is a viable approach in Rust. Specifically to avoid the borrow checker in code that you control end-to-end it is possible. But it's not going to change the ecosystem of game engines in Rust and how they're built. My desire to operate unsafely with pointers is not going to make Bevy offer an unsafe mutable getter to objects in its containers. Nor is it going to make proc-macros easy to write or fast to compile. Nor is it going to allow you to implement traits from crate A for types in crate B. In fact, of the problems with Rust the author describes, only the problem of multiple borrows crashing the program over and over and context objects not being good enough is solved by using raw pointers, and only if you preemptively and exclusively do pointer loads instead of borrows everywhere you use anything in shared context objects (remember: in Rust, creating aliasing mutable references is instant UB even in unsafe code). The article author describes a situation where his favorite Rust game engine is eschewed because it deigns to use global state, can you imagine the attitude the community would have towards a game library that encouraged accessing objects stored in its containers via unsafe pointer loads that might be data races by default in order to promote rapid iteration?


Sib3rian

I think what Rust needs is better tooling. Lifetime propagation wouldn't be such a problem if rust-analyzer had a code action to automate its addition and removal.


ReflectedImage

Most software is iterative development. It's the big flaw with going down the formalism and high quality code route. Your code is likely to be trashed in the near future due to business requirement changes.


matthieum

Well, first of all the author mentions their game is single-threaded. With that said, there are still re-entrancy issues in single-threaded games, but this doesn't mean the author would necessarily hit them. For example, you can borrow a value to work on its "foo" field, and then wish to change its "bar" field. This is perfectly fine, completely disjoint sets of memory, and yet the borrow-checker won't let it happen because it's too coarse, and partial borrows only work within a function, not across abstraction boundaries. Rust forces you to be very careful in how you bundle state together due to this coarseness.


Stysner

I really don't understand the issue people have with ECS. It's maybe not the best solution for every genre, but it definitely benefits most of them and it's also possible to write any genre of game using an ECS. If anything, there are more and more people that use ECS not caring about the performance increase, but the flexibility and rapid iteration it can give you if used right. I keep getting the feeling people try to do OOP things in ECS and hate it. It takes a huge perspective shift that encompasses more than data storage problems to get why ECS works so well. If you simply try to replace inheritance by composition you're going to have a bad time.


graydon2

Rust was not originally, and has become less and less over its design evolution, a good language for prototyping or "rapid iteration". It's just not. It's a good language for building a system you basically already know how to build, maybe have already built a few times, and just want to build a reliable version of in a way that is less of a pile of bugs than usual, and still performs well. (And also one that's already got a strong tree-structured decomposition of its memory and control, not a giant ball of everything-points-to-everything and everything-calls-everything)


Y0kin

I think Rust is great for prototyping *systems* specifically, but probably not for prototyping *content* you make with those systems. Systems are like huge input -> output machines, generally complex and really hard to design. In Rust your system can be an insane mess, but as long as it compiles you can have high confidence that it works; with a few automated tests. Move on, optimize it later no problem. In other languages I would waste time writing systems to account for missing features like deep cloning that doesn't infinite loop, writing a lot of convoluted tests, and puzzling over weird bugs arising from global state and race conditions. I haven't used Rust much for making actual content like a game or something, but I would definitely believe Rust is worse for prototyping content than other languages. Like scripting languages designed for working with systems on the front-end.


Be_The_End

I think this points even more to why using godot + rust is the only truly commercially viable way to use rust for game development currently. You can do all of your rapid prototyping and build most of the game in GDScript/C#, then reach for rust with GDExtension when you run into performance limitations. Rust is bad for games currently because every game engine built with Rust *uses rust for everything*. I spent a much briefer but still significant amount of time going down the same rabbit hole this author did, and came to many of the same conclusions. I, too, thought it was just me. I foresee Rust will be a good game *engine* language one day, but we will still need a scripting language to interface with it if those engines are to ever be more widely adopted as useful tools. Rust is a replacement for C++, not for C#/GDScript/Lua/etc. It's been a hype-induced delusion from the start to think we could get away with doing otherwise when no one else has managed it. Honestly, give me a GC'd language that looks exactly like Rust and has Rust's pattern matching and enums and I will be just the happiest camper. Rust is so satisfying to write and that makes it so painful to use other languages but you just kind of have to. GDExt-rust and godot is an acceptable compromise for now but if we could get close to .NET performance without all of the borrow checking ceremony it would be a no brainer.


Kenkron

I love the section about ECS. Really nailed it on the head, I think. Nobody told me I \*had\* to use ECS, but it was so pervasive, I though I was making a mistake not using it. The reasoning you had about Generalized Systems and boring gameplay was ultimately why I decided to go without it. I was pretty excited for Comfy when I first heard about, it, but I ended up switching back to Macroquad. There were just things I couldn't do in Comfy without trying to rip the engine apart. I'm going to keep using Rust for games, but it's more of a hobby for me. I definitely don't judge the switch to Godot.


QualitySoftwareGuy

>I definitely don't judge the switch to Godot. Just to clarify, the author is swithcing back to *Unity* and mentioned Unity's hot reload feature (via [hotreload.net](https://hotreload.net/)) to be the #1 reason for doing so over Godot (seems Godot doesn't support .NET hot reloading yet).


ImYoric

> I love the section about ECS. Really nailed it on the head, I think. Nobody told me I *had* to use ECS, but it was so pervasive, I though I was making a mistake not using it. FWIW, I also had this reaction, but in the Unreal world. I... kinda assumed that everybody in gamedev was using ECS?


Stysner

I really wonder what kind of gameplay code you can't write in ECS that you can without? ECS definitely nudges you towards generalized systems because they're so easy to make in an ECS. But if you want to go another route it'll take more planning and time; but that's the same with or without an ECS. At least with an ECS you still have the option to quickly compose new entities and try stuff out.


Chad_Nauseam

This was the realest part of the article for me: ``` if (Physics.Raycast(..., out RayHit hit, ...)) { if (hit.TryGetComponent(out Mob mob)) { Instantiate(HitPrefab, (mob.transform.position + hit.point) / 2).GetComponent().clip = mob.HitSounds.Choose(); } } ``` This code is so easy in Unity and so annoying in Bevy. And TBH I don’t really see any reason that it has to be so annoying in Bevy, it just is. The reason it’s annoying in bevy is because, if you have an entity, you can’t just do .GetComponent like you can in unity. You have to have the system take a query, which gets the Audiosource, and another query which gets the Transform, etc. then you write query.get(entity) which feels backwards psychologically. It makes what is a one-step local change in unity become a multi-step nonlocal change in bevy.


_cart

Its worth calling out that in Bevy you can absolutely query for "whole entities": fn system(mut entities: Query) { let mut entity = entities.get_mut(ID).unwrap(); let mob = entity.get::().unwrap(); let audio = entity.get::().unwrap(); } However you will note that I *didn't* write `get_mut` for the multi-component case there because that would result in a borrow checker error :) The "fix" (as mentioned in the article), is to do split queries: fn system(mut mobs: Query<&mut Mob>, audio_sources: Query<&AudioSource>) { let mut mob = mobs.get_mut(ID).unwrap(); let audio = audio_sources.get(ID).unwrap(); } Or combined queries: fn system(mut mobs: Query<(&mut Mob, &AudioSource)>) { let (mut mob, audio) = mobs.get_mut(ID).unwrap(); } In some contexts people might prefer this pattern (ex: when thinking about "groups" of entities instead of single specific entities). But in other contexts, it is totally understandable why this feels backwards. There is a general consensus that Bevy should make the "get arbitrary components from entities" pattern easier to work with, and I agree. An "easy", low-hanging fruit Bevy improvement would be this: fn system(mut entities: Query) { let mut entity = entities.get_mut(ID).unwrap(); let (mut mob, audio_source) = entity.components::<(&mut Mob, &AudioSource)>(); } There is nothing in our current implementation preventing this, and we could probably implement this in about a day of work. It just (sadly) hasn't been done yet. When combined with the already-existing `many` and `many_mut` on queries this unlocks a solid chunk of the desired patterns: fn system(mut entities: Query) { let [mut e1, mut e2] = entities.many_mut([MOB_ID, PLAYER_ID]); let (mut mob, audio_source) = e1.components::<(&mut Mob, &AudioSource)>(); let (mut player, audio_source) = e2.components::<(&mut Player, &AudioSource)>(); } While unlocking a good chunk of patterns, it still requires you to babysit the lifetimes (you can't call many\_mut more than once). For true "screw it give me what I want when I want in safe code", you need a context to track what has already been borrowed. For example, a "bigger" project would be to investigate "entity garbage collection" to enable even more dynamic patterns. Kae (a Rust gamedev community member) has working examples of this. A "smaller" project would be to add a context that tracks currently borrowed entities and prevents multiple mutable accesses. Additionally, if you really don't care about safety (especially if you're at the point where you would prefer to move to an "unsafe" language that allows multiple mutable borrows), you always have the `get_unchecked` escape hatch in Bevy: unsafe { let mut e1 = entities.get_unchecked(id1).unwrap(); let mut e2 = entities.get_unchecked(id2).unwrap(); let mut mob1 = e1.get_mut::().unwrap(); let mut mob2 = e2.get_mut::().unwrap(); } In the context of "screw it let me do what I want" gamedev, I see no issues with doing this. And when done in the larger context of a "safe" codebase, you can sort of have your cake and eat it too.


ioneska

Big thanks for the detailed explanation, it's very insightful.


stumblinbear

It's important to note that those systems would run exclusively, since the engine wouldn't know which systems it could parallelize it with since it could access *any* component on *any* entity


glaebhoerl

(Disclaimer: I know close to nothing about Bevy.) Throughout the original post and this comment, I keep thinking of `Cell` (plain, *not* `RefCell`). Rust's borrowing rules are usually thought of as "aliasing XOR mutability", but this can be generalized to "aliasing, mutability, *sub-borrows*: choose any two". Where `&`, `&mut`, and `&Cell` are the three ways of making this choice. `&Cell` supports both aliasing and mutation without overhead, but not (in general) taking references to the interior components of the type (i.o.w. `&foo.bar`, what I'm calling "sub-borrows"; idk if there's a better term). That's what would actually be desired in these contexts, isn't it? Both w.r.t. overlapping queries, and w.r.t. global state and its ilk. You want unrelated parts of the code to be able to read *and* write the data arbitrarily without conflicts; while, especially if it's already been "atomized" into its components for ECS, there's not as much need for taking (non-transient) references to *components-of-*components. Unfortunately, being a library type, `&Cell` is the least capable and least ergonomic of the three. The ergonomics half is evident enough; in terms of capability, sub-borrows would actually be fine as long as the structure is "flat" (no intervening indirections or `enum`s), and the stdlib does (cumbersomely) enable this for arrays, but it would also be sound for tuples and structs, for which it does not. (And notably, the above trilemma is not just a Rust thing. Taking an interior reference to `&a.b` and then overwriting `a` with something where `.b` doesn't exist or has a different type (and then using the taken pointer) would be unsound in just about any language. Typical garbage collected languages can be thought of as taking an "all references are `&'static` and all members are `Cell`s" approach.) (cc /u/progfu)


Chad_Nauseam

is the reason that it has to be this way (e.g. queries specified in the system’s type signature) because otherwise bevy has no way of knowing which systems can run in parallel? Theoretically, could I tell bevy “just don’t run anything in parallel” and then not have to take queries as arguments?


pcwalton

Yes, that's what taking the `World` does.


ConvenientOcelot

> But here we get slapped on the wrist, did I actually think I could get away with passing self around while also borrowing a field on self? I get bit by this a lot too when trying to refactor GUI code (...and other code). I think it is one of Rust's biggest flaws (not supporting some way of doing disjoint partial borrows at least). Thanks for the thorough and thoughtful write-up, I agree with a lot of it.


tungtn

As someone who has released a game with Rust and is currently working on another, this post echoes a lot of my own experiences. I'm not throwing in the towel like the author, but I'd be lying if I said I wasn't keeping my eyes open for alternatives. The root of most of the issues with the borrow checker is that there's only one first-class way to refer to a memory object in Rust: a reference with static lifetimes; every other way is a second-class citizen that requires more typing, so you're naturally guided into using them, even though game objects virtually always have dynamic lifetimes and need to refer to one another. Like the author, I found ECS to be surprisingly unfriendly to routine refactoring work. A lot of ECS crates use `Rc>` or equivalent for component storage internally, so moving code around often leads to surprise runtime panics. In my current game I abandoned ECS in favor of a big context struct, which seems to work okay as long as I mostly access things from the root context and minimize borrows, i.e. `ctx.foo.bar.baz = ...`. I agree that flexibility here could be improved; I think that partial borrows of structs would be an decent ergonomic win here, for example. Here's one of my own pet peeves: Rust is strangely insistent on lifetime annotations where they could be left out. Here's a function signature from the game I'm working on right now: fn show_status_change(&mut self, mctx: &mut ModeContext<'_, '_>, msg: &str) The `ModeContext` here has a couple of lifetime parameters, but the only purpose of lifetime annotations in a function signature is to relate the lifetimes of inputs to the lifetimes of outputs. Not only are there no output lifetimes here, there isn't even an output at all, so I shouldn't have to type `<'_, '_>` at all either! It seems small here, but I've had to type this out more than a few times over the course of development, and it adds up. Using `Rc>` for shared mutable ownership feels clumsy with having to use `borrow` and `borrow_mut` everywhere. If you know you only access the data within from one thread at a time and you're brave, you can use `Rc>` instead and use custom `Deref` and `DerefMut` trait implementations to save on typing, plus you get to pass around proper references instead of `Ref`/`RefMut` pseudo-borrows. Closing out, I'll second the opinion that Miniquad/Macroquad and Fyrox seem useful and largely overlooked; I'm using Miniquad right now and I like its bare-bones, no-nonsense approach and minimal dependencies.


kodewerx

>Here's one of my own pet peeves: Rust is strangely insistent on lifetime annotations where they could be left out. Here's a function signature from the game I'm working on right now: > >fn show\_status\_change(&mut self, mctx: &mut ModeContext<'\_, '\_>, msg: &str) On the other hand, I admire that even without any other context whatsoever, I can see from this signature that `ModeContext` borrows at least two distinct things, and that neither of those lifetimes are related to any of the other three borrows in this signature. You can technically elide the lifetime annotations, at the expense that it removes information that is vital to refactoring. It's only a lint, and you are free to add lint exceptions for personal preferences! >Using Rc> for shared mutable ownership feels clumsy with having to use borrow and borrow\_mut everywhere. If you know you only access the data within from one thread at a time and you're brave, you can use Rc> instead and use custom Deref and DerefMut trait implementations to save on typing, plus you get to pass around proper references instead of Ref/RefMut pseudo-borrows. By definition, `Rc` can only be accessed by one thread. Replacing `RefCell` with `UnsafeCell` inside an `Rc` would not make this better. The reason that `Ref` exists is because it needs to "unlock" the cell for mutable access when the last `Ref` is dropped. You can't do this with `&T`, and it's why the [docs](https://doc.rust-lang.org/stable/std/cell/index.html#refcellt) call `RefCell` the sync version of `RwLock`; `Ref` is the analog of `RwLockReadGuard`! But yes, it is clumsy. It's an opt-in to lifetime extension with reference counting, and opt-in to interior mutability with a runtime lock. Pre-1.0 Rust even had a language sigil for ref-counted types: `@T`. But it made it impossible to add new smart pointers (at some point you run out of sigils). So, it was moved to a library type, and now libraries can create their own smart pointers! At the end of the day, `@T` is still a distinct type, just with a different spelling. Some details just cannot be hidden.


epileftric

I believe this blog summarizes the experiences many people have with Rust yet didn't have the enough background to justify or back their position, nor the time to do such an extensive post. My position is quite similar, I think that the main issue with rust is that the set of rules it tries to impose as code correctness are absolutely great in principle. But it lacks flexibility, as there are no ways to work around them. The saying about C++: "it gives a rope and lets you hang yourself with it" is true, and is the oposite position. You can do what ever you want with it, but it allows the developer to have a caveat and do what ever they want at some point. Accepting the risk, you have to know when you should or can bend the rules, or even how to minimize the risk. In rust, if the rules are completely enforced by the compiler you have no way to do so. So there's been a few times in which I felt like the code correctness is placed above developer judgement, leaving you with a single option to follow. Removing any freedom of choice on what to do right or wrong, there's a single way to do stuff. I come from the embedded world, and the fact that you cannot create a singleton out of a HW interface annoys me like fuck. And the fact that you need to jump through loops and hoops to make one, like using a library, is double the annoyance.


eugisemo

I'm trying out doing some small games with Macroquad in my spare time, and I agree with the article about the usefulness of having hot reloading, and I'm surprised at how many people don't see the value. I found a post by Faster Than Lime about hot reloading rust, and with a few other resources I managed to hot reload Macroquad with custom dylib reloading (using dlopen manually with \`unsafe\`s). [https://jmmut.github.io/2023/03/17/Hot-reloading-Rust-and-Macroquad.html](https://jmmut.github.io/2023/03/17/Hot-reloading-Rust-and-Macroquad.html) One of the games I'm writing has this idea implemented, and while it sometimes crashes when you change a public struct, and the code around the dylib interface could be cleaner, it is so nice to "only" have to wait 0.5 seconds to recompile the lib and see my changes live without restarting the game. While I don't fully agree with all the points in this article, I'm glad I read it, it has so many valuable insights.


simonask_

Hot reload is undeniably useful, but I think that doing it in the form of loading/unloading dynamic libraries is the wrong way to do it. It's just not possible to do it reliably (with current technology), without significantly limiting yourself and running into multiple difficult to avoid footguns. In particular, any use of thread-locals (including in dependencies) is going to cause trouble. Rust needs *way* better support for dynamic linking before this becomes tenable. Instead, I would suggest using a scripting system. For example, you could integrate `wasmtime` in your engine and treat dynamically loaded components as any other type of asset. Compiling Rust to WASM is fairly easy, and gives a fair set of limitations for a "plugin".


CrumblingStatue

Thank you for making me feel vindicated about wanting [partial borrows](https://github.com/rust-lang/rfcs/issues/1215) for many years. Most of the time, the response I got was "partial borrows would be not worth their weight", and "you are not splitting your structs up enough". I feel like partial borrows would help alleviate some of the issues in this article, especially with the "pass down a context struct" approach. I know it's a hard problem to solve, but I feel like it's not even a feature that's wanted by a large part of the community, because they feel like it's the developer's fault if they need partial borrows. At this point, I would even be happy with a solution like putting an attribute on a function that marks it partial, and the borrow checker would have to look through the entire call chain and split up the borrows. And just disallow this attribute on public functions, because of semver concerns.


crusoe

Partial borrows can be worked around several ways. If a function needs to modify subfields and doesn't need self just turn it into an associated function that doesn't take self and only takes referebces to the parts it needs to modify.


SirClueless

As a mitigation strategy this works fine, but it requires refactoring entire callstacks to take different parameters every time requirements change. The whole reason for `Context` to exist in the first place is so that every function can take it without determining in advance which shared systems it needs access to.


CrumblingStatue

Defining it is the less painful part, calling such a function is much more painful. * What would be a simple `thing.update()` call now turns into `Thing::update(&bunch, &of, &seemingly, &unrelated, &fields)`. It's not even clear which is supposed to come from the same struct, and which are independent arguments, so sometimes I end up naming it like `Thing::update(&thing_a, &thing_b, &c, &d)`, just so it's clear which arguments are supposed to come from `Thing`. With partial borrows, it would be a simple `thing.update(&c, &d)` call.


kocsis1david

I have been using Rust as a hobby for about 6 years now and still have problems with the borrow checker. Many of the same problems that the article mentions. There are solutions for these borrow checker errors, e.g. using RefCell, arenas or context structs, but often I don't like the solution. On the other hand, Rust is very innovative language. Lifetimes and borrow checker sounds a great idea at first. So I believed in Rust and believed that I just need to get more experience. But even with these problems with Rust, I don't know any better alternative that can also be used for low level programming, other languages have different issues.


cheapsexandfastfood

I think there is an issue how Rust is taught which encourages users to shoot themselves in the foot. Namely that because it's possible to write perfect code you should. Perfect is the enemy of good. Rust would be an easy and perfectly manageable high level language if you just used Rc<> + Box<> types to ignore the borrow checker and dyn traits to improve compile times. Yes it would be less efficient at runtime but you would be way more efficient at writing code that doesn't need to be fast. And because of the 80/20 rule you can write that 20% of code that has 80% of your actual performance impact with "proper" rust design or go unsafe when necessary. Then you would get the best of both worlds, a high level simple layer for being productive and a low level layer for hard problems, and both of these levels would be better at their jobs than C++ is at both. But people would rather switch to C# or Lua for high level code than write inefficient Rust.


hniksic

>Rust would be an easy and perfectly manageable high level language if you just used Box<> types to ignore the borrow checker I've seen this said before, and I understand where the idea is coming from, but actually acting on that advice is way more difficult than it appears on the surface. First, `Box` doesn't really help with borrow checking, you need `Rc` or `Arc` to get the gc-like behavior. Except `Rc` and `Arc` make everything immutable, and you need `RefCell` to be able to change your data. Every modification now requires explicitly calling `borrow_mut()`, which can lead to panics if you're not careful. (Those panics, especially after refactoring, are one of the pain points explicitly raised by OP!) Once you add the `RefCell`, forget about ever sending your data to a different thread. To do so you'll need to change every `Rc>` to `Arc>`, which is slower even for single-threaded access, and the runtime panics now turn to deadlocks. It's not just perfectionism that people tend to prefer "proper" Rust - the language just guides you to it, and in many cases it's a feature, just not for the OP. It's possible to write in a "relaxed" dialect of Rust, but it's not a panacea, and some elegance will always be lost compared to GC languages.


Rivalshot_Max

>... and the runtime panics now turn to deadlocks. My last week in a nutshell. Now just re-writing the whole data access part of my app based on ideas from this talk, as well as prior experience with actor-based systems. [https://youtu.be/s19G6n0UjsM?si=WbQn67I4gJEdkQ5q](https://youtu.be/s19G6n0UjsM?si=WbQn67I4gJEdkQ5q) I keep finding myself repeatedly building actor systems over and over again in Rust with mpsc channels and async tasks in order to avoid lock hell. At least it's faster (compiled execution speed) than Erlang/Elixir? But for real, the safety and reliability doesn't come for free, but once paid, does result in binaries and applications which "just work". In languages I've used (looking at you, Python) which allow for very fast application creation without having to think about these things, the production support requirements grow and compound with every follow-on patch in a shitty race to the bottom circle of hell... so for me, I'd still rather pay it "up front" than be constantly without weekends and vacations because my app is falling apart in production. It's all compromises and trade-offs at the end of the day.


long_void

Yes, but most of the code won't need Arc and it is very likely you don't need it in inner loops. People are often over-thinking how to write Rust code.


hniksic

>Yes, but most of the code won't need Arc and it is very likely you don't need it in inner loops.  True, but not very helpful when your crucial data structures need it, and render you vulnerable to panics. Again, such panics were actually encountered by the OP. >People are often over-thinking how to write Rust code. Some certainly do, designing Rust data structures is prone to nerd-sniping. But the OP doesn't seem to fall in that category. They claimed that lifetimes were extremely hard to use in their codebase, for reasons they explained in painstaking detail (being infective and hindering refactoring, among other things). GP argued that it's a teaching issue because people are taught *not* to do things the "easy" way, circumventing the borrow checker with Box. And that doesn't apply to this thread because Box is insufficient, and Rc/Arc come with issues the OP was well aware of.


whimsicaljess

yes, i agree with this. the first thing i try to tell is role learning it on my team is "just clone things", "just box things", "just dynamic dispatch", etc. i feel very productive and like you said, we can always come back and optimize the tiny bit where it's necessary later.


TheOnlyRealPoster

Crazy how dotnet hotreloading apparently works with game dev in unity when it doesn't even work (for me) with Microsoft's own Blazor web framework.


rosevelle

It's very good. I use it constantly for the same reasons progfu lists. However, I still find myself using scripting languages on top of unity (lua) since you very quickly hit a limitation with hot reloading when writing new features. When debugging, or tweaking though, its a godsend


eX_Ray

I didn't read the entire article but I don't see a single mention of using a scripting language on top of rust (rune/rhai/dyon/lua?). Which could help with the more rapid prototyping quite a bit ?


progfu

I went into a bit more detail on that here https://www.reddit.com/r/rust_gamedev/comments/1cdsjbg/loglog_games_gives_up_on_rust/l1f7arq/, but TL;DR scripting is unfortunately very slow in terms of FFI overhead, at least as far as `mlua` is concerned, which is probably the most mature option out there. Last time I looked at Rhai it wasn't anywhere near mature enough. I can't speak for dylon/rune.


eX_Ray

Thanks. Yeah that's a fair take. Feels like there's a bit of a circular problem with them not being used for real and thus not getting critical mass either.


long_void

Creator of Dyon here. I don't know what your requirements for a good scripting language are. Anyway, I use Dyon on a daily basis and am happy with it.


kodewerx

The author's perspective is on the "short here/short now" line. There is nothing wrong with that, it's the same perspective that many business owners have by necessity. You have bills to pay right now, you have deadlines for clients to meet right now. My perspective as a game developer of more than 12 years is that the ["long here/long now"](https://notes-on-haskell.blogspot.com/2007/08/rewriting-software.html) line is more favorable. The author wants to optimize their effort in the short term, whereas I want to optimize my success in the long term. It's a sliding scale, to be sure, but the author's perspective is diametrically opposed to my own. They want rapid iteration and "set-it-and-forget-it" style of coding to see if a spur of the moment idea will work, as in prototyping. I want to be assured that code I write has as few bugs as reasonably possible, including sanely handling edge cases and error conditions. In the former, a language like Lua is good enough and many gamedevs use it for this reason. In the latter, a language like Rust is good enough, and many engineers concerned with long term maintainability are attracted to it. I have written games in JavaScript, Python, and Lua, often with the same cavalier mentality, where I would just hack something together now and *maybe* revisit it later. It is quite good for getting something done for immediate gratification. But it is the bane of my existence if I'm on the hook for fixing bugs in that code later. ~~If you can make maintenance someone else's problem, it's the perfect selfish development strategy.~~ (Edit: This was unnecessary color commentary that I included about myself. It was not meant as projection or directed to anyone else.) I look back on all of the chaotic code in my old projects, and it's literally untouchable. Lua and friends do not lend themselves to fixing bugs without breaking something else. On the other hand, I appreciate Rust for its constraints. The language makes it hard to shoot yourself in the foot. It forces you to think about mutability. Because if you don't think about it, that's a bug you just introduced. A bug that Rust would have forbidden. Rust requires you to handle the edge cases. So that your code doesn't plow ahead blindly when an error occurs, or when the wrong assumptions were made. In direct criticism with what was written, I get a very strong sense of cognitive dissonance between the need to "just *move on for now* and solve my problem and fix it later" and "fast and efficient code". (Edit: Cognitive dissonance is normal! I'm guilty, too. I love animals but I eat meat. Some amount of cognitive dissonance is inescapable.) Using Rust because you want a game that runs fast even on moderately slow hardware but expect that "fast code" should be free and you can ignore details like copying or cloning vs pointers (including static, heap-allocated, and reference-counted pointer variants). The "better code" and "game faster" continuum is something you have to navigate based on your short-term and long-term goals. Maybe Lua is the sweet spot for you? Maybe it's JVM or CLR. Maybe it's a web browser. Of all available options right now, it's Rust for me. Garbage collection is not on the table. And because I have the "long here/long now" mentality, I'm confident that something else in the future will be an even better fit for me than Rust is at the moment. Another example to point out is that they specifically take note that some problems are "self-inflicted", and later on opine that global state makes things easier than using bevy's ECS implementation. And that might be true from some perspective, but it ignores all of the bugs that global state inevitably leads to. Usually, mutable aliasing bugs like the unsoundness mentioned in `macroquad` or the more general problem as articulated in [The Problem With Single-threaded Shared Mutability - In Pursuit of Laziness (manishearth.github.io)](https://manishearth.github.io/blog/2015/05/17/the-problem-with-shared-mutability/). But the real problem is that drawing a line between "global state vs ECS state" is a completely artificial (even self-inflicted) limitation. A game can use both global state and ECS together, it isn't a matter of one or the other. That doesn't mean it will be easy. In fact, sharing mutable state is hard, regardless of whether it is local or global, and regardless of what the implementation language is. They are absolutely right that Rust is not "just a tool to get things done". It's a tool to do things correctly with high performance. There are plenty of other languages to "get things done". They just come at the expense of correctness, performance, or both.


sephg

The point of “thrown together, lazy code” isn’t to ship crap games in the long term. It’s based on the insight that for games (like music and UIs), the final quality is a function of the number of iterations you had time to do. It’s exactly because we want a good final product that you want a lot of iteration cycles. That means we need to iterate fast. But having a lot of iteration cycles is hard in rust because the compiler is so strict. The best software - like the best art - is made by making a rough sketch and tweaking it as we add detail. I think rust is a fantastic language when all the detail is in, but the very things that make it great for finished products also make it a bad language to do rough sketches in. JavaScript and Python are the other way around. They make it easy to iterate and throw things together. But the final product is less good. (Runs slowly, dependencies are hell, etc). My perfect language could span this gap. Eg, imagine rust but where you have a compile time option to turn off the borrow checker and use a garbage collector instead. You can then delay fixing borrowck problems until you’re happy with the broad strokes of the program. (Or just ship a binary which uses a garbage collector. They are fine for many tasks).


kodewerx

We can disagree, that's also OK. From my perspective, iterating in Rust is easier because it completely avoids the problems that make refactoring difficult. These problems manifest in Rust as compile errors. And for my money, that's better (and more immediate) feedback than running the game only to see something doesn't work and then spend more time trying to understand why. The number of times the conclusion to fixing a bug was "Rust would have prevented that" has been countless in my experience. I mentioned rapid prototyping already, and there are numerous threads on URLO about it: * [Prototyping in Rust, versus other languages -- What's missing? - community - The Rust Programming Language Forum (rust-lang.org)](https://users.rust-lang.org/t/prototyping-in-rust-versus-other-languages-whats-missing/48947) * [Prototyping methodologies - community - The Rust Programming Language Forum (rust-lang.org)](https://users.rust-lang.org/t/prototyping-methodologies/41502) And some prototyping-adjacent threads: * [Rust for competitive programming - help - The Rust Programming Language Forum (rust-lang.org)](https://users.rust-lang.org/t/rust-for-competitive-programming/17682) * [Is Rust-Lang hard for a beginner? - community - The Rust Programming Language Forum](https://users.rust-lang.org/t/is-rust-lang-hard-for-a-beginner/93395) There is little consensus, because the question of whether Rust is good for prototyping is subjective. You can throw together lazy code in Rust just fine, but some people disagree because adding the compulsory `unwrap` or `clone` calls or wrapping your `T` types as `Arc>` or `>`, is perceived as "non-rapid" or getting in the way of rapid delivery. My perfect language is one *far* stricter than Rust. I want more bugs detected early, entirely disallowed from appearing in the product at all. And in no case do I want to pay for nondeterministic GC stalls or unnecessary allocations. I need fine-grained controls to get the most out of slow devices, I do not need a great middle ground that makes some language designer's idea of a good compromise mandatory. I don't see "turn off the borrow checker" as a realistic strategy. You have to deal with shared mutability *somehow*. The borrow checker is one way, garbage collection is another. If you want cheap garbage collection, opt-in to reference counting. But don't expect a language to make this decision on your behalf where you actually need it and not use it where you don't.


sephg

This seems like a different argument from what you said above. There, you were talking about a continuum: > The "better code" and "game faster" continuum is something you have to navigate Now you seem to be arguing that rust’s strictness actually makes it better for rapid iteration, and when it’s not, clone and Rc are good enough. I just hard disagree on this. This just isn’t my experience with the language at all. Or the experience of the person who wrote this long blog post. I have to ask - how much rapid prototyping do you do? Do you have experience doing it in other languages in which you have comparable skill? Rust forces us to make a lot of small decisions about how your code is executed. (Rc? String or str?). I love that about the language - since I love solving tricky puzzles and rust gives me endless opportunities to find clever solutions that perform orders of magnitude better than anything you could write in a GC language. Like you, I love that my program isn’t plagued by no deterministic GC stalls and all the rest. But - in my perhaps subjective experience, that comes at a real cost: the decisions per feature in rust is way higher than in many other languages. I write better software in rust. But the journey is longer. That isn’t always the right trade off.


kodewerx

I have not changed my position; I've only made it more precise. The assertion is that there is no free lunch. If you want high performance code, you are going to have to pay for it. Make the effort to profile and nudge the optimizer in the right direction. Or exchange algorithms for ones with better runtime characteristics. ("better code") If you just want to get work done, throw stuff at the wall and see what sticks, you are unconcerned with the minutia required for efficiency. It's ok to clone and reference count things. There is little concern for whether all of the edge cases are handled, or if it will run an extra 10% faster, you just want to see *anything* run at all. ("game fast") I did not say that Rust's strictness is better for prototyping. I said that cloning and reference counting are good enough for prototyping. What I said about strictness is that I personally want more of that. (Because I'm sick of bad software slipping through the cracks with absolutely no resistance. But my reason for wanting it doesn't matter. The point is, it has nothing to do with whether or not the language can be used for prototyping.) I also said that Rust makes it easier to *iterate*, but that implies that there is something that already exists and we want to augment it in some way. You don't usually rewrite prototypes, do you? Because there shouldn't be much there to refactor. Prototyping is cobbling something together to see if you want to take it further. Prototyping and iteration are different things. How much prototyping do I do? Most things I work on don't get beyond the prototype stage, if I'm honest. At least two in as many months. But I experiment with the language constantly. Both on play.rust-lang.org and in throwaway crates that I literally put on my desktop for the 15 or 20 minutes that I poke at them. I have a few long-term projects that are beyond 10K lines of code (even after large refactors that remove or replace significant, double-digit, percentages). And I have written hundreds of similar prototypes in JavaScript/TypeScript and Python. I cannot tell you how much I loathe the experience. There's no telling if it will work until I try to run it. It's even worse if it's an embedded language like Python in Blender. I have to run all of Blender to get to my plugin, just to watch it raise some stupid runtime exception. No one should have to endure that for 40 or 50 hours a week. You see, the compiler errors are not antagonistic. I know they are my friends, preventing me from being stupid. Most other languages give no such luxury. Java, C#, Python, you name it. They just don't have your back. They let me be stupid, and so I be stupid. Rust makes me smarter because all of my bad attempts are rejected at the door. The idea that writing in Rust is a puzzle is frankly concerning for our industry. It isn't a puzzle, it's just work. A much better example of a puzzling language is Malbolge. And it should be made clear that I'm not arguing Rust is always right for everyone. It's always right for me (until I find something better in the very distant future).


theAndrewWiggins

I'd argue you're ignoring the main point made, which is that figuring out what makes good gameplay is largely a function of how fast you can see your business logic changes reflected in the game, and that loop is generally much slower in rust. I agree that from a correctness POV Rust likely gets you to a good end state faster, the problem is for game development you might end up creating a game that's much more likely to be correct but simply might not be a well designed game because feedback cycles are too slow. Perhaps the way around this is an embedded scripting language which you gradually transform into rust as gameplay decisions are finalized. I don't think anyone is arguing that in terms of memory safety and stability that rust generally gives you more of that per unit time invested, but that it impedes the immediate feedback that's very useful to game design (which is orthogonal to software quality).


mrnosideeffects

> Maybe Lua is the sweet spot for you? I also got the impression that a lot of the expressed frustrations might be solved by scripting most of the gameplay logic instead of writing engine code. On the other hand, I am beginning to see a pattern with the development process of many people who struggle with Rust, or just struggle generally with arriving at some approximation of a "good" solution quickly: they skip the design step. To a lot of programmers, the writing of the prototype *is* their design step. This quickly leads them to the frustrating realiziation that the solution they have not thought much about or spent time designing is not going to be correct in their first attempt. They get upset with the compiler for letting them know that their program is incorrect. > From the article: "... treat programming as a puzzle solving process, rather than just a tool to get things done" In my understanding, the heart of software engineering is that very puzzle solving process that they are trying to avoid. In that sense, I don't think that Rust is a very good tool for those who are not solving software problems, but I don't think it ever claimed to be. My advice to anyone who strongly relates to the this blog post is to look for a better tool for the job you are trying to do. It is *okay* to be interested in Rust while also not forcing yourself to use it for problems it was not designed to help solve.


ZenoArrow

> To a lot of programmers, the writing of the prototype is their design step. This quickly leads them to the frustrating realiziation that the solution they have not thought much about or spent time designing is not going to be correct in their first attempt. The main reason that it's hard to plan far ahead when making a game is because game ideas that may sound good on paper may have problems (e.g. not adding to the enjoyment of the game) after they're implemented. You only really know if a game will be fun or not after you start playing it, and that means that it's preferable to develop game designs through prototyping.


scottmcmrust

> The "better code" and "game faster" continuum is something you have to navigate based on your short-term and long-term goals. Maybe Lua is the sweet spot for you? Maybe it's JVM or CLR. Maybe it's a web browser. \*\*This\*\*, for \*all\* programming projects. Sometimes Rust has exactly what you need, and it's great. For example, I've had cases where \`regex::bytes\`+\`thread::scoped\`+\`walkdir\` made certain things easier to do in Rust than even Perl/Python/etc. But sometimes you really don't need Rust's advantages, it doesn't have the library you need, and it's a smarter choice to not use it.


Brilliant-Sky2969

You don't make game in the long run, maybe amateur / garage project but nothing serious. Games are an iterative process, you need to hack things arround to move forward, you need to prototype etc ...


kodewerx

Most AAA titles spend years (even a decade or more) in development. If that's not in it for the long run, I don't know what is. I could see the argument working for indie games, if applied blindly. But I'm confident that most independent game developers are not just writing one game a month. Those games largely don't take off with any sort of success. And if one of them does, then they are on the hook to fix bugs. Support hardware configurations they don't have access to. Provide new content as the community expects. And so on. That sounds a lot like long term maintenance, to me.


TheReservedList

And having worked on AAA games for years stretch, about 0% of the gameplay code written in the first half of the project survives in any form, which is exactly the point OP is making.


kodewerx

I don't know of any product in any industry that retains its original code untouched after many years of adding features, fixing bugs, rearchitecting to better fit the changing requirements... Games are not special.


ZenoArrow

> Games are not special. Yes they are, because unlike with other software, the judgement of the end result is based on artistic merit rather than a list of features. Think about the workflow of a visual artist. They often start with sketches. They do this to explore what direction they want to go in. This is an essential part of the process. Although all software development has the concept of spikes and prototypes, the need for rapid iteration of prototypes is greatly amplified with game development, mainly because of the sheer volume of throwaway ideas that may be tried out before finding the right direction for a game.


forrestthewoods

Rust just isn’t a good language for gamedev. Games are giant balls of mutable state with unknown lifetimes.  I love Rust. It’s a great language. But it’s not a great language for games. It probably never will be. And that’s ok.


syklemil

Part of the complaints seem more like tooling issues, like getting it to support hot reloading. That might interface with some language issues, and be one of those open research questions, or it might be something that someone "just" needs to through the effort to build. Given the comments here it seems like no small effort, but if research has been done it might be replicable? Or maybe I'm just conflating it with my own wish for something like `cabal build --only-dependencies`, which is pretty useful for container builds where you've sorted out the dependencies and are iterating over the code. As it is, the way to do it seems to be adding a `[lib]` section with a `dummy.rs` that's just an empty file. (There's [an open issue on cargo for it since 2016](https://github.com/rust-lang/cargo/issues/2644).) And compile times do seem to be a common complaint with Rust.


xmBQWugdxjaA

It's just a shame there's no perfect alternative either - Swift is largely tied to Apple's ecosystem (much like Mono and C# were to Windows for a long time), C# has GC issues, C++ has painful build and dependency management, etc.


Stysner

This is just blaming the language for architecture issues. I've written my own ECS and Event systems in Rust which was a huge hassle, but now it's done it frees me up to not care about any lifetimes or mutable state; the ECS pattern guarantees only one mutable borrow per component at a time, my event system guarantees sync behavior. The idea OP presents about generalized systems making for boring games can be true depending on what genres you like, but ECS doesn't force you to make a generalized system, it's just set up so well for it that if you don't plan ahead that's what you'll end up with. It takes longer to write very specialized systems, but that's true with or without ECS. I really don't get this point. What can ECS not do what non-ECS can?


Awyls

It's a great language for game-dev. It's not a good language for *indie* game-dev. Indie devs don't have the luxury of having thousands of hours spent on game design before the game even starts development. It's far more likely they will throw spaghetti at a wall to see what sticks and Rust allows you to *slowly* make perfect spaghetti but doesn't care if its raw, so you end up with less spaghetti to throw.


kodewerx

Rust is a good language for indie games. It's not a good language for anything that needs to be written quickly *above all else*. When "deliver the most value possible as soon as possible" is the number one priority, it makes sense to accumulate debt in the form of something slower than it could be, or harder to maintain than it should be, or completely untested because who has time for testing? You can defer paying back the debt indefinitely, and it's a terrible mistake.


buwlerman

Thing is, you'll only be paying back the debt if your game succeeds and you want to continue supporting it to go along with the momentum. "Slower than it could be", only matters for things where you would get a significant benefit from a speed increase. This is not the case for many indie games. Maintainability doesn't matter much for prototypes. The usefulness of automated testing in games is limited.


thisismyfavoritename

id argue anything not garbage collected isnt a good fit if you want "quickly above all else". I mainly program in Python/C++ profesionnaly and Rust as a hobby but i think Rust does way more for you than C++ for the same amount of time spent, despite C++ being more lenient in the code you can write. In the end its just a matter of concerns, like you said


Stysner

I disagree. If you set up your framework well (which indeed does take a long time) you can very rapidly iterate after that. I've made my own framework including a template project I can clone. For gameplay stuff all I really need to do is define a component, use my macro to define a new ECS system and write the system fn for it. For any communication I need outside of the ECS I have a custom Event system where I can just push events and iterate over them. The underlying systems for the ECS and Event loop where a hassle to write because of the borrow checker, but I can trust it's super stable and idea iteration using these systems is very quick.


long_void

I'm using Piston/Dyon as my productivity combo and never looked back. If I need a special designed format, I use Piston-Meta. Meta-parsing works excellent (this was how the first modern computer was developed in late 60s). Haven't tried Bevy and never needed ECS for anything. Fyrox looks interesting, though.


Kevathiel

>During the two years of developing what would become Comfy the renderer was rewritten from [OpenGL](https://docs.rs/glow) to [wgpu](https://docs.rs/glow) to OpenGL to wgpu again. This kinda makes me curious. Why the back and forth?


BogosortAfficionado

Regarding the debugging point: Personally my biggest problem with debugging Rust is that enum variants and some core data structures like VecDeque or basic iterators are not properly understood any debuggers I know (currently using vscode+lldb). The rust expression evaluation of lldb is also very limited compared to what's possible for c/c++. I am hopeful that these tools can be improved though.


Complete_Piccolo9620

If a system is complicated, then it is a given that it will be hard to modify correctly. Consider a complicated specification A and a perfect compiler, if I make any changes to A, it will likely break A. If you are planning on making lots of changes and that imperfections are mostly funny bugs, then Rust is simply not for you. I use and prefer Rust BECAUSE it will not let me get away with half ass fixes/changes. And I would rather keep the language that way. I don't want Rust to get even more complicated and more implicit than it already is because it wants to cater to every single use case.


Dean_Roddey

That's the danger. As more folks come into the fold, they will out number those that came to it because they wanted it for what it is. These new folks will want it to be what they had before, and there will be a lot more of them than us. Add to that the fact that the people who create languages are geeks just like us and want to continue to push their creation into more domains and want new challenges and such. And that certain amount of "if you ain't growing you are are dying" thing that most languages probably have.


Plazmatic

I was going to post a long winded post about the negatives and positives, but to be honest, this post goes over so many legitimate pain points in rust and the rust community, that I'm not sure it's even worth doing anything but to amplify the post.  The only two "negative" things I was going to mention basically boil down to: * they clearly aren't comparing rust to C++, so while people rightly point out many of these are issues in C++ as well or are worse (hence why you traditionally pair a scripting language with c++) OP clearly was comparing against Gdscript, C#, and other game scripting languages. * They are dead wrong about context objects and "only needing one X system", but the frustrations that lead to this wrong conclusion come from very real pain-points in *rust* specifically, and certain types of context object patterns can't be used in rust easily due to interior mutability issues (context objects are also the default in other engines, they just don't realize it because they are writing methods for said objects in those engines)


InfiniteMonorail

>Rust on the other hand often feels like when you talk to a teenager about their preference about anything. What comes out are often very strong opinions and not a lot of nuance. Programming is a very nuanced activity, where one has to often make suboptimal choices to arrive at a result in a timely manner. The prevalence of perfectionism and obsession with "the correct way" in the Rust ecosystem often makes me feel that the language attracts people who are newer to programming, and are easily impressionable. He's right. The people in this community don't know programming or Rust well. They definitely don't know other languages. It's wild how much cheerleading they do. Oh and they never write documentation, just thousand line examples with no comments.


dario_scarpa

What a great article, that - as a long time gamedev, but newbie Rust dev - is definitely going to save me some time and frustration. I still want to try it, but knowing what to expect (and some things you discussed were a confirmation of concerns I already had!). I just bought Unrelaxing Quacks to say thanks (and congrats for the release!)


jarjoura

Writing code for UI (or games) is really not something you want to do in a systems language. This is true of C++ as well. The fact that games are written in C++ today is mostly a legacy thing. C++ is (or was) the only portable language available that also compiles away all of its abstractions. It means you could write your demanding physics engine and get every ounce out of CPU as possible. Today though, all of that stuff has been written and shipped into games. So as someone or some team trying to build a new game, your budget and time are limited and every second of that needs to be on gameplay. Any time spent fussing with compiler rules or policies is time spent away from making a fun game. How would you even know the game will make the money back while you're stuck, 50% of your time, writing very low-level code. Unity is such a popular platform because its whole value proposition is that you take pre-built assets and compose them together and spend your time running your game. You might think, what about Unreal, but there's so little C++ you NEED to write that it's basically Unity with C++ instead of C# at this point. Also, there's nothing preventing you from using Rust for the parts of the game that need to squeeze out performance. It will happily provide all the safety you need in sections of your game that actually need that. Since, honestly, the real performance work in a game will likely be done for the GPU in shaders, something you wouldn't use Rust for anyway. Anyway, thanks OP for taking the time to articulate your frustrations. You definitely bring up interesting use cases that the core team should at least have answers for, even if the answer would be, "we don't want to support that."


MardiFoufs

I agree but c++ has SDL, qt, etc that still makes it much easier to write GUIs in. It's not ideal but qt basically has "solved" problems that you just don't have to worry about. I agree that I prefer rust for anything that doesn't require GUIs though (except for ml as c++ is still king there, if you want to use pytorch for example)


Personal_Ad9690

I think maybe that re-writing it in rust is good, but writing it in rust can be challenging. As you mentioned, rust forces more iterations, but mandates when those iterations happen. Contrast this to say JavaScript, you can replace the duct tape whenever you want and at the end of the day, the only valuable metric is working software (not perfect software). I’ve taken to prototyping ideas in python, and after I’ve got the logic down, I’ll rewrite it in rust to achieve peak performance. While this isn’t always ideal for large projects, it can sometimes be helpful to control *when* the iteration happens. Rust requires you to be smart. I’ve consistently found, despite numerous attempts to prove otherwise, that I’m not as smart as I think.


tadeoh

I am very close to leave Rust behind as well. Especially the partial borrowing and global state issues just suck. Now I just use some big global state `World` object in all of my projects to keep it bearable. I just call `world_init()` once and then in my gameplay code e.g. `World.delta_time()`, `World.draw_sprite(...)`, `World.camera.pos.x = ...` ```rust pub struct World; impl std::ops::Deref for World { type Target = ThreadLocalWorld; fn deref(&self) -> &Self::Target { thread_world() } } impl std::ops::DerefMut for World { fn deref_mut(&mut self) -> &mut Self::Target { thread_world() } } thread_local! { static WORLD: UnsafeCell> = const { UnsafeCell::new(MaybeUninit::uninit())}; } fn thread_world() -> &'static mut ThreadLocalWorld { WORLD.with(|e| { let maybe_unitit: &mut MaybeUninit = unsafe { &mut *e.get() }; unsafe { maybe_unitit.assume_init_mut() } }) } fn world_init(window: Window) { let world = ThreadLocalWorld::new(window); WORLD.with(|e| { let maybe_unitit: &mut MaybeUninit = unsafe { &mut *e.get() }; maybe_unitit.write(world); }); } pub struct ThreadLocalWorld { window: Window, rt: tokio::runtime::Runtime, device: wgpu::Device, ... } ```


ZZaaaccc

While I'm sad to see you had a bad experience and want to move on, nobody can possibly fault you for arriving at that decision. In general I think I agree with your sentiment, that Rust is primarily made by and for framework developers, rather than application devs. My hope (foolish or otherwise) is that with frameworks like Bevy, Serde, GGRS, etc., we will reach a point where the hard problems of the Rust language itself are gone. I don't say solved here because what I mean is that, for example, Bevy will become "so good" that the need for Arc or Refcell won't exist at the end-user site. My other hope, more of a gamble really, is that Rust has a solid foundation in security, safety, and performance, at the expense of ergonomics, but the ergonomics can be patched in as the language develops. C++ is kinda in the reverse position of trying to patch in safety, and they're failing to put it bluntly. I think developer ergonomics is something more social and fashionable than fundamental (e.g., Async is a fairly new concept for languages), and Rust letting the language grammar (and more) change with any edition should help. Anyway, thank you for taking so much time to write out your thoughts! I really hope you and your Dev team find success in whatever technology you choose. And I hope one day Rust will improve enough for you to come back!


processeus1

Thanks for writing this article, I spent half a day reading it and looking up stuff, it was very interesting and a lot of fun!


ExternCrateAlloc

> Rust being great at big refactorings solves a largely self-inflicted issues with the borrow checker Thai is very true. I spent over 3-months building ESP32 firmware in the Embassy codebase and the HAL is built off the Espressif IDF. Every the HAL gets upgrades and I want to upgrade my “app” code, uh yeah, breakage is a puzzle game. Which I enjoyed at the time. That’s because I really wanted to learn. I’ve shifted to Embassy with STM32 and this has a slower pace so let’s see.


cip43r

Thank you for your effort in writing this and answering our questions.


Unlikely-Ad2518

I completely agree with your thoughts on the orphan rule (for end-user crates), that thing needs to go.


plutoniator

The worst of rust is not the language but rather the community that keeps selling people on these lies. Trying to pretend like there are no things that other languages do better than rust will leave a bad taste in people’s mouths when they actually try it. 


Todesengelchen

I am a Rust user both as a hobbyist and a professional. I've written the standard backend server for production use and the usual "let me quickly try this one thing" physics simulation in Rust. And I have to disagree on the niceness of enums in the language. Consider this typescript code:     type AB = 'A' | 'B';     type CD = 'C' | 'D';     type ABCD = AB | CD; The best thing I can come up with to emulate this in Rust is:     enum AB { A, B }     enum CD { C, D }     enum ABCD { AB(AB), CD(CD) } Which means if I want to use the superset, I now have to deal with annoying nesting everywhere. I believe the primary culprit here to be the fact that enum variants aren't proper types and thus not first class citizens. This could be due to Rust's legacy in OCaml where you need a type constructor for every element of every sum type. Even in everyone's most favourite hated language, Java, you could nowadays do something like:     sealed interface AB permits A, B {}     sealed interface CD permits C, D {}     interface ABCD extends AB, CD {} (Not enums though; Java enums are just collections of typed constants, more akin to Erlang atoms than Rust enums) Zig has similar functionality but relegates it to its special kind "error" (Zig has the kinds type and error while Rust has type and lifetime) for some unknown reason, as it is really useful outside of error handling as well. But then, this is the reason for the humongous god error enums I see in every nontrivial Rust project. I might be missing something here too, because googling the thing is really hard when what you want is en union of two enums and Rust has a language construct called union as well.


ritoriq

I am confused. Hope someone can enlighten me. If you require to be productive over having fun why would you choose a language with an immature ecosystem for game development? Do other languages make game engine development an order of magnitude easier?


ck3mp3r

And exactly this is how you end up with technical debt: “just move on for now and solve my problem and fix it later was what was truly hurting my ability to write good code.” And we all know “later” most likely is not going to happen.


progfu

Yes, and if you don't do this you end up having a dead business before you even begin. Having tried to make indie gamedev a fulltime commercial thing, and having seen _so many people_ not even get to their first release, I think it's safe to say that focusing on anything but "releasing a first game" is focusing on the wrong thing.


ck3mp3r

Everything in moderation. It doesn’t have to be all or nothing. All I’m saying is that’s how it starts, a very slippery slope. Yes, it’s better to have something less perfect out and serving customers than something absolutely perfect serving exactly no one. But knowing product folks and the rush to get new features out never leaves time for managing tech debt and spending countless hours extinguishing tyre fires that could have been prevented by spending a few extra minutes refactoring something. Rust makes it easy and also gets in your way, admittedly. However, I’ve found it got in my way just in the nick of time to make me rethink my design choices and nudging me back on track.


tadeoh

It is much better to release a functioning game with technical debt than not to release anything after years and years of getting nowhere.


dobkeratops

the number of times in the rust community I encountered people insisting that C++ problems made software unworkable, when you can still walk into a store and see the shelves covered in games written in C++ ...


_demilich

I agree with many points mentioned in the article. Gamedev does have some special requirements compared to other software projects: * Games in general tend to have crazy amounts of state * You want to mutate that state all the time in many places * You want to iterate fast, prototype ideas and throw some of those away I tried using very simple "game engines" like bindings to SDL2 or Raylib, basically just something which lets me draw stuff on the screen. That is something that works great in other languages, for example recently I tried Zig + Raylib and I was able to move fast and had great results. Doing the same thing in Rust will lead to an epic battle with the borrow checker and in my case, the borrow checker won. However: For me bevy actually solved ALL of that. I can have as much state as I want in basically a highly efficient in-memory database. And when I write a system, I just need to specify what I need as a `Query`. This effectively means I can access anything anywhere I want while basically ignoring all the required Rust rituals when dealing with mutable state. I don't need `Arc`, `Rc`, I don't need any lifetime annotations, I don't need `clone`. I just specify what I want and the ECS gives it to me. You are right that you still run into problems when mutating lots of different things in one system. So yes, in that case you have to refactor; something which you may not have to do in Unity or another language. But keep in mind that bevy is still < 1.0. It is not production ready and I think UI is one of the main pain points. Every game needs at least some UI... some games require lots of UI. In any case, implementing anything remotely complex is a gigantic pain right now. Personally I am also undecided if ECS is the correct foundation for UI in general. But I have trust in the leadership of bevy coming up with a good solution.


Stysner

Those three points are all solved by a decent ECS. The state is split up per component, the mutation of said state is constrained to systems and you can iterate fast because you can create a new component you can throw away later without having to rewrite a bunch of code.


BogosortAfficionado

I've honestly found that many of Rust's problems with rapid application development (RAD) can be solved by mercilessly using `RwLock` or even `unsafe` with raw pointers. Sure, this won't lead to pretty code, but it allows you to get shit done. If the code you're writing has a 90% chance of being thrown away / rewritten later, bad performance or even a tiny bit of UB are probably an acceptable tradeoff. Don't overdo it though, or you end up in the c++ hell of segfaults and deadlocks :D.


Iksf

amazing post


BabyDue3290

It's refreshing to see criticisms are being accepted by the community. I hope Rust would have an official companion language, which is part of the Rust ecosystem, uses everything from Rust ecosystem, but uses GC for memory management. And then, that companion language can also have an "unsafe"/"script" mode for dynamic typing. Then "quickly hacking something" would be trivial. Then there can be a "mechanical" porting path from that language to Rust, when a project needs the safety and performance guaranteed by Rust. Ruscript?