Hacker News new | ask | show | jobs
by moldavi 1382 days ago
For a single player turn-based game, I'm not so sure the tradeoff is as obvious as everyone says, after coding one in C++ and another in Rust.

It's turn based, so I don't need AAA game multi-threaded performance. It's single-player, so cheating doesn't really hurt anyone.

I use sanitizers to catch memory problems, but there have only been a few of those over the years. Maybe ECS just isn't that vulnerable to memory-safety problems?

The borrow checker prevents iterator invalidation problems, but most of my functions are read-only so there's not really a chance for those anyway, or many other single-threaded race conditions for that matter.

After this long using Rust, I'm noticing more and more that the borrow checker is imposing a lot of artificial complexity, when I compare it to my C++ version. We're often told that Rust is hard because the underlying problem is hard, but I've encountered many cases where that's not true, it really is just the borrow checker. And unfortunately, most of them are problems that Polonius won't fix.

Rust has a lot of great features (enums and cargo!) but I think one should think carefully about their domain and architecture before choosing a language, lest they pay too much cost for too little benefit.

Just my two cents!

5 comments

> It's turn based, so I don't need AAA game multi-threaded performance. It's single-player, so cheating doesn't really hurt anyone.

I don't understand this, just because it's turn based it doesn't mean you can't squeeze more juice out of your CPU if you want to have all kinds of good graphics, fancy animations, fancy complex environments (lots of moving pieces, etc), physics simulation, etc.

Turn based is just one style of play but there's plenty of turn (or pseudo-turn) based games (like JRPGs) which struggle with platform limitations. One could even argue that games like Final Fantasy XIV are turn based.

> I use sanitizers to catch memory problems, but there have only been a few of those over the years. Maybe ECS just isn't that vulnerable to memory-safety problems?

To expand on this a bit. In games, objects generally fit into two categories:

1. Short-lived temporary objects, usually stack-allocated or arena-allocated, etc. e.g. a point vector, or matrix.

2. Long-lived objects with indeterminate lifetime. e.g. a character or item.

In the case of long-lived objects, there are various strategies you could use to make it safe e.g. using object IDs or custom smart pointers to refer to other objects that potentially might go missing instead of pointers which are bound to be corrupted.

Generally you want to register all of your long-lived objects in one central location that will handle memory management. Manual, reference counting or GC are all viable strategies depending on requirements.

I like Rust and would use it for games but mainly because of the package management.

> It's turn based, so I don't need AAA game multi-threaded performance.

Then you could just use a GC'd language. For example, you could use C# in Unity.

> We're often told that Rust is hard because the underlying problem is hard, but I've encountered many cases where that's not true, it really is just the borrow checker. And unfortunately, most of them are problems that Polonius won't fix.

Out of curiosity, what are your favorite examples?

I would be curious to know more about this case too, fwiw!

Personally, I've only struggled a bit with the borrow checker in a couples of cases: trying to have self/circular references when trying to model OOP, and when fiddling/interacting with some low level async code. Both were unnecessary in my case, and each situation was caused by a lack of understanding.

Fascinating!

Do you use async closures? That's the only case where I felt the borrow checker is a bit behind. Also I haven't checked in a few months and I know there was some work ongoing.

That forced me to start organising the code in a different way which is less functional but easier to understand, so it's not crystal clear what's happening.