I’ve been working on a games related rust project for a handful of months now - I didn’t know too much at the time but was just curious to try it out, but it’s become pretty involved at this point. I definitely don’t consider myself a huge rust evangelist or anything, but I’ve had a pleasant and interesting enough time with it to want to share some thoughts with anyone interested. I am mostly interested in trying to explain the experience of working with rust, instead of trying to tout it’s features or anything. If you try to make something with it, will you have a good time with it? Hopefully I seem pretty level-headed about what’s good or bad.
The big selling point for rust is it’s overall “safety” - memory safety but also safety with threads. Performance of the language is another selling point, but in all honesty you really aren’t getting anything you couldn’t with c/++. It achieves these things with a very in-depth compiler and linting system. The language imposes a lot of ‘rules’ you wouldn’t traditionally think about while just coding. Generally when you make a mistake it tries to guide you in the right direction, but doesn’t always understand what you’re attempting to do.
I think for indie games, neither of these main characteristics of the language are really that important. Games, especially 2D ones are single threaded, and only really run slowly when weird things are happening like issues with a slower scripting language, or unoptimized game logic. Plus in order to enjoy those features you have to get over the hurdle of the language’s more restrictive technical nature - it’s definitely harder to get a grasp of than c# or similar languages.
The big upside of rust for game development is that it’s compiler has a ton of built-in features for cross-platform building. This even includes the web. You can super easily test out builds of your games in most engines with a couple lines of config and for the most part they just work. For webgames it’s VERY nice to not have to work with emscripten at all.
Overall things aren’t too complex - lines end with a semicolon, you declare functions just with
fn. You can pull functions and types out of a module anywhere without having to import it, if you know where it is - for example I can call
std::time::Instant::now() If i want to get the current time, and don’t have to remember to go back up and add an import for it.
One of the first things you hear about rust is that it doesn’t have classes. I was weirded out by this at first because I was worried I wouldn’t be able to make objects and have them have functions on them, like
monster.attack(). Fortunately this still IS a thing in rust. You create a struct that just defines what data is in it, and then below it a separate
impl section where you can write functions available on that struct type. It’s not so bad when you get used to it, and actually kinda useful because you can organize different files that have
impl for the same type, if you want to keep things separated by category of function.
A big part of the language is a pretty complex macro system, which if you aren’t familar, is LIKE calling a function, but instead of performing logic, the macro gets expanded into some actual code in your file when you compile your app. It doesn’t actually edit any of your code files though, it just happens in the background before compiling.
Rust tries to get you familiar with it quickly - in order to print out to the console you use a macro:
println!("Hello world :)"). You know it’s a macro because it ends in an
!. What it’s doing under the hood, instead of calling some library that has access to the console, your code adds extra lines right there access the console directly. Again this only happens WHEN you compile so your own code files just look normal. A LOT of libraries do things with macros that are pretty crazy. Personally, when I see a library that uses a ton of macros for everything it can be kind of scary, because it can add or change logic in your code a lot.
This is the part of rust development that gets tougher for newcomers.
You’ll be writing a function that needs access to some shared info. Maybe your enemies want to track the location of the player. How do enemies get information about the player in THEIR update logic? You could try putting the player as a property in the enemy struct, a reference like
&Player. Now the rust compiler wants to know: “hey, what happens if the player gets deleted BEFORE the enemy does? You need this thing called a
lifetime that says this enemy will only last as long as the player, or that the player always outlives enemies”. GENERALLY SPEAKING, if you run into a compiler error asking for a lifetime, you don’t actually need one, which I think scares off a lot of beginners. This is where rust’s safety features just get in the way of you trying to prototype something quickly.
So what’s going on, what do you do about this? It takes a bit to understand how lifetimes and ownership work. I liked this video on it a lot when I was trying to start. The “real” solution to the question above is to think about WHEN the enemy needs access to the player and WHY. Maybe you only need to know the player’s location or something. The easiest thing to do is make your enemy update function accept a player reference as an argument, if you only need it for that function you don’t have to worry about the lifetime. More complicated rust game libraries (I’ll get to those) build out a whole set of tools to make it easy for your objects to “request” references to other game objects.
The other things that help you in cases like this are “smart pointers” - these are types that you wrap AROUND an object you want to be able to share places and pass around more easily. So while you might not be able to pass around copies of the reference to your player, you CAN copy an Rc, which is a type you can store the player reference inside of safely. I’d really recommend you look into the smart pointers and ownership before you really write anything. Once you get a grasp of how they all work, it does start to make sense why rust overall works the way it does, and why programming in it makes you do all these weird patterns you aren’t used to. But for a long time, months even, this stuff just sat in the back of my head pestering me to write code “better” by avoiding them. They are useful!
Like I mentioned earlier, rust doesn’t have a class system like you might expect in c++, c#, java etc. It’s definitely scary to wrap your head around how you might build out something complicated without having a big hierarchy of object types and children. Rust can do a lot of that same stuff, but in it’s own way called traits.
The simple way of explaining traits is that they let you organize your types not by what they ARE (animal, cat, dog) but just by what they DO. Traits don’t require or add extra properties to your structs, only functions.
The classic Animal class has
talk() functions, where cat and dog child classes have different values for what they say (meow, bark). In rust you’d accomplish this by making two separate traits,
CanTalk. Then when making your cat or dog struct type you say: “Yes, these animals CAN do those things, and here’s how”. What’s useful about this is that maybe down the line you are making a
Robot, which can’t breathe and Definitely isn’t an animal, but it CAN talk, which means it’s just automatically able to speak to your cats and dogs. The point is that traits let things that are totally different categories communicate as if they are the same type, given that both satisfy the same requirements (defined by the trait).
“Hey, if you can satisfy a few requirements about data or functions that take this shape, I can give this type a WHOLE LOT of other functions that build on those and give extra functionality”. A more concrete example of this would be something like a collision system: If you create a Shape trait that requires a struct have functions that return:
- A list of edges of the shape
- A point of central mass
You can write a function that takes in Anything that implements shape and have it collide with anything else. Or you can add functions onto that type that help it calculate it’s area, which you don’t have to add individually per type of shape. It does this WITHOUT restricting you on what other things you can implement for it, and without controlling what types of data your struct has, so you can keep memory footprints as small as they need to be.
Traits can do a lot of cool things but don’t expect to really “get it” for a while. In my project I use them to define things I want to be able to “swap out”, so I can let users choose if they want to use a certain library for creating windows, or hopefully different graphics apis.
To me the workflow actually getting rust and building your code is SO much better than CMake or other tools for compiled languages (dotnet/mono etc).
Rust has an installer tool called ‘rustup’ - it’s pretty painless to get going, though on Windows it will ask you to get Visual Studio build tools for c/c++, which can take some time to download on top of things. After that rustup is what you use to update to new versions, but it’s all through the command line.
Once you have it installed you’ll have to start getting used to
cargo which is the tool for actually building and running your rust projects, and installing external libraries.
Unlike something like python, you don’t install libraries globally, instead on a per-project level. In the base of your project you create a
Cargo.toml where you list libraries you need, and can give the compiler configs on how to compile the code (for example enabling optimization, or settings for cross-platform builds). This file serves the purpose of something like CMakeLists.txt, but has the ergonomics of package.json if you are familiar with JS development. It’s WAY nicer to use than using C or C++ - you simply add a line for each of your dependencies you want, and the compiler will just grab them when you go to compile - no need to git clone a bunch of stuff yourself.
Having a package manager is the main reason I’ve enjoyed using rust over other compiled languages. It’s great to just browse https://crates.io/ and find things that might be useful for my project. Another neat thing is rust has a built-in documentation builder, so anything on https://crates.io/ also has at least a page you can go to to check out what the functions look like, or what structs are in a library.
This is one of the big sticking points for me. I think getting your initial grasp on the language is not so bad. If you have something in mind you are trying to write, it’s totally possible to just start fiddling around, and when you run into strict compiler errors, figuring out the pattern that solves them usually teaches you a whole lot. A lot of rust people will just tell you to “read the book” - But honestly I’ve only ever referenced parts of it. There is also something called https://rustlings.cool/ - an official set of “code puzzles” where each teaches you something important about the language. These are good because if you don’t know what you want to make with rust, they at least give you a goal of something you need to figure out.
When you are just toying around with a game engine you only run into so many types of problems that verbose documentation can fix, it’s more just a matter of figuring out which functions you need to call and where they are in the library.
That being said, documentation is one of those areas that CAN have some pretty lackluster areas to it. I talked about the automated docs tool above, which is really neat that every package has a site that works exactly the same you can use to reference, but that does NOT mean all documentation sites are equal. Developers have to annotate their code above every function with comments to generate text on these sites. So the less prolific the author is the more you’re left in the blank as to what everything does or how to use it. Often times I’ll check out a library knowing specifically what I want to use it to do, but the docs site just has NO information on how to practically apply the things the library provides. From there the only thing you have left is to dig into the libraries github, and hope they set up a code examples folder.
The main problem is that most libraries don’t give you a lot when it comes to questions like “how do all these types fit together” or specific use cases “how do i get the library to do THIS with my data”. The biggest example of this recently for me is with the crates
glutin - rust based providers for opening windows and setting up opengl. They recently made a big update that separates the functionality a bit more with a new package called
raw_window_handle. In theory this is really good, because any other windowing library can adopt it, and
glutin can use the new handle instead of relying on winit. So you can use
sdl2 and mix-and-match. The problem is, even though they did this there are so few examples of how that actually works, especially when you mix in opengl into it. So I have the trouble of trying to actually read the library code to figure it out, or ask the developers to write something as an example that uses the specific set of libraries I was interested in seeing glued together. It just needs more actual documentation to be of any use to anyone coming from the outside
Because it’s such a technical language, rust attracts a lot of coders but quite a few less ‘creative’ types - so right now it feels like there are more rust game engines being made than actual games. That being said, it’s still a pretty new language, so don’t expect anything on the scale of Unity, GameMaker, etc. In my opinion most of the best libraries out there function most closely to things like LÖVE2D, so they are more “frameworks” than engines.
ggez sets the basis for what rust framework code works like - You create some sort of struct that represents your main game state, then you implement event handler traits on it for initializing and updating it. You get an update step for game logic, and a draw step, a function that needs to accept a context for drawing things. It then handles things like polling for keyboard events and maintaining FPS for you. But that’s about it, you just get graphics, sound, input. You’ll have to pick out everything else from the package manager if you want things like collision.
macroquad is a library largely inspired by the raylib game library. What is sort of neat about macroquad is that it relies on largely the developer’s own tech, instead of commonly adopted rust libraries (for things like graphics, windows, audio). It doesn’t enforce any sort of GameState struct, you just create a for loop in a
main function. It does some “magic” with a macro that sets things up for you, and makes all the graphics drawing globally accessible functions without having to pass around a graphics context everywhere. Some rust users have an issue with that because it does rely on some “unsafe” code - but it’s really only unsafe in situations where you are trying to draw things with multiple threads at the same time. It also has a lot easier setup for building to the web than ggez.
tetra is the first and only library I really got in depth with. It’s pretty stable but not actively maintained anymore as the developer is building out another library called nova intended to replace it. Tetra’s style is fairly similar to ggez, you need a gamestate struct. In my opinion the layout of it’s functions makes a lot more sense though - things are sorted into modules, and those modules have functions that require the graphics context to do anything. So when you want to look up a function,
tetra::graphics:: - your code completion will show everything you can do. There are other modules like
tetra::input and such too. The other thing that appealed to me is that the website for tetra contains extra documentation outside of just the default auto-generated website - there are actual tutorials on how to set it up and get started making games, and a pong tutorial you can follow along. It’s not a ton, but it’s miles better than a lot of other libraries that only have code example folders. Tetra also does really well at 2D sprite rendering performance, more than anything else on this list. Though nothing here performs poorly, they are all good for basically any kind of game.
bevy is the premiere game engine in the rust community for now, and the foreseeable future. At some point they seem to want to compete in the space of larger open source game engines like Godot. It’s got a huge set of features built in like lighting, physics, and such, and is far more suited for 3d games than any of the above too. The goal of bevy is more or less to build as many of the new/experimental game engine features as it can. Out of everything so far, it takes the most time to set up your project and get a sense of what to do to build your game. That’s mainly because it uses a game logic paradigm called ECS which it builds EVERYTHING around, including setting up the app and the window. They are particularly excited about this because in a sense they are beating Unity to the punch on making it the focus of the engine. bevy also has it’s own “book” which probably is the best primer any rust game engine has for itself. The main downside to bevy is that there is no GUI editor for it yet, and for building 3d games that’s basically a necessity. They are supposedly beginning to shift some of their focus over to it though.
ECS as a concept makes a lot of sense once you understand what’s important about rust. The best way I can put it is that ECS is to Monobehavior/Gameobject trees as rust’s traits are to OOP. Basically:
- An Entity is a thing that holds a bunch of different components
- Components are things that just hold data, and maybe some functions for manipulating it. A good example would be a health component
- Systems are just functions, that request groups of entities that meet certain requirements, and perform logic on them. A system might request all entities that have a
healthcomponent and all entities that have a
spikycomponent (+ position components), and then check all of them to see if anything should be taking damage. This makes systems a lot like traits in that they don’t care WHAT the entities are, just what things they are able to do.
If you are going to get into gamedev with rust, you are probably going to have to get familiar with this architecture. In a sense it makes the most sense considering the way rust works. Even if you are using one of the simpler game engines, if you are making something large you might want to look into trying one of the ecs libraries like hecs out alongside your framework.
Fyrox is a 3d game engine with a functioning GUI editor - making it by far the most “complete” engine available in rust. It doesn’t always get the same fanfare that bevy does, particularly because it doesn’t make as many strides for “experimental” features. The programming style with Fyrox is a scene tree where nodes are individual components - It’s more similar to Godot than Unity. It seems very capable but doesn’t have the exact same “cool factor” as something like bevy, so I’m not sure how to recommend it.
If you are interested in rust but hesitant to learn any libraries that might get deprecated in a year, or their specific coding styles, there are a lot of bindings to more popular/traditional C libraries out there. SDL2, Sokol, Raylib to name a few. There is also a tool that lets you write rust code as scripts for godot!
I also want to mention two other entities in the rust space that are of note to gamedevs.
Chevy Ray, the creator of the game Ikenfell, has announced and been live streaming development of a rust game framework called px. I think this is important as it’s the first time an indie of longer-standing notoriety has approached rust. It’s not available yet, but it will be interesting to see the decisions an established dev who is planning on using rust long-term makes compared to the 2D frameworks that exist currently. I think when this does release it will probably contend pretty highly, so it may be worth waiting to just check this out first instead.
Embark is a larger scale game studio that is putting most of their effort into rust as a platform for game development. They don’t really have a public engine of their own, instead sponsoring existing engines, but a lot of their work goes into making ancillary tools like profilers that will really help out developers in the long run. It seems like their goal is to bridge the gap that exists between rust and established languages in terms of game tooling. Here’s a list of some of the things they have worked on.
I think rust is a really great language for low level performance-critical work like game engines, but has a lot of hurdles for anyone just trying to test it out and make simple games. It’s surprising to me that nobody has really set up any of their engines to focus on a scripting language like lua or similar, but use rust as a backend to keep things stable and fast underneath. That is sort of what the project I’m working on is about, but I’ll figure out how to talk about that later.
The main pros of working with rust is that it’s way easier to set up and get compiling than most other things, and you get an actual package manager that makes working with third party libraries so much better. It’s basically the easiest compiled language to set up and get cross-platform builds too. Some of the things with traits and macros make really complicated code look super easy to write, if you rely on libraries that make use of them. And for the most part, everyone working on rust packages are VERY performance minded - everything has benchmark pages and you more-or-less know that it’s pretty good code just from looking things over.
The two main negatives are first that the language has a lot of concepts that do just stall development until you get proficient enough with the language. It will take months before you are working with it without the little hiccups feeling like your app is completely written wrong. The other half of the negatives are all rooted in it’s infancy as a language. Not enough stuff is documented well or has a good community. There will be situations where no amount of googling will help you figure out a library. Lots of the libraries out there are all competing for “canonization”, so you will see new shiny libraries every week and every few months something else will stop development. Everything is pretty unstable and that makes it hard to pick what packages to use.
Importantly, if you want to make something for consoles it’s very hard to tell how possible that will be, or when that will be addressed by any of the engine projects out there. The only hope is it takes your game long enough to make that someone else figures out how to get it compiling on switch while you were busy.
Anyways that’s most of what I have to say on working with rust. It’s definitely worth looking into if you want to get into a faster compiled language, because all the rust tools are way easier to get into than C, and its NOT Java so that’s a plus. This is probably a lot so if you read through everything that means a lot honestly. Hopefully I get my devlog written soon because that’s what I’ve been working in rust for in the first place.
If you have any questions or want advice on things to pick out or try out, I’d be happy to try and answer.