Originally published at Codegram's blog
A couple of years ago, back when I was doing my first steps with Vue, I decided to create a videogame using this framework. It’s called Live, and it’s a mostly text-based survival game with a very minimal UI (you can play it here). I had a lot of fun creating it, people had fun playing it, and it helped me learn a lot about Vue.
Nowadays, the hype around the office revolves mostly around Rust. I’ve been reading a bit The Rust Programming Language book and doing rustlings, but we all know the best way to learn something is by doing it. Codegram’s Retreat™ offered me an excellent opportunity to build something with Rust, although doing a big project felt a bit overwhelming.
But hey, history repeats itself, and what worked before should work again, so why not build Live again with Rust? With the business logic already defined and clear, I could focus on the code.
The biggest challenge was, of course, to think and program “the rust way”. Coming from JavaScript as the main language, playing around with other languages like Ruby or PHP doesn’t feel that much different. Even when you add strict typing with TypeScript, the general way of doing things doesn’t change that much.
Working with Rust, suddenly you become aware of how memory works, how different types of data gets stored in the stack or the heap, and that there are 12 different types of numbers 🤯. You learn to appreciate the weird and sometimes frustrating JavaScript quirks, in exchange for simplicity. On top of that, you need to learn completely bizarre concepts like ownership and lifetimes. And even when you encounter a familiar thing like an array, turns out that you can’t push new elements into it! And you can’t have different types in it! You easily get into a point where you just don’t know how to do the most basic things. “How the hell am I supposed to have different types of items in my inventory!”
Luckily, not everything is entirely different and frustrating. You still have functions, modules, const
and let
. Even though everything works a bit differently, you can get your mind around that easily. And cargo
feels like a super-powered npm
.
I followed a pragmatic approach and tried to have a functional game before worrying too much about following Rust’s best practices. That means a lot of mutability, cloning things, and when something doesn’t work, just try to add an *
or a &
and see if it solves it. Make it work, understand it later. That’s my motto! It might not be the most academic way to learn a new language, but it works for me. Let me give you an example.
Most of the game revolves around items. Finding items, consuming them, using them to craft other items (even though that is not implemented yet, I wanted to leave the data structure in place to be able to add the feature later). A standard item, like wood
, should have an id
, name
, and description
. A consumable item, like a berries
, should also have other properties like the points of water
and food
it adds to your stats once consumed. This meant that I really had different types of items, but since Rust doesn’t have inheritance, I could not have a BaseItem
to extend. To avoid getting stuck with this for hours, my first approach was to add all the properties possible to all the items. So I ended up with wood
having water: 0
and food: 0
properties, which is pointless. I also had to add a consumable: true
property to check and inform the user if the item could be consumed or not. It worked, although it wasn’t ideal. But at least I could go on with the project, and that's important for morale!
So once I had all the features of the game in place, I came back to this problem that I knew it should be done differently. At this point, I was a bit more confident because I had already developed a game with Rust. If I tried to solve the problem the right way when I first encountered it, it would have been a painful and frustrating process, and probably would have failed anyway because I just didn’t know Rust. With David’s and Txus’ help, we explored a few approaches. Having different types of items didn’t work, since most of the time I needed to work with a single Item
type. Also, it didn’t feel right having to repeat the common properties for each one. We tried to create an Item
trait and have all the item types implement it, but since a trait has no knowledge of the attributes of the struct that implements it, it just didn’t work without having to duplicate a lot of code for each implementation of the trait. And that didn’t feel right either.
If you have little or no knowledge of Rust, don’t worry if what I’m saying makes no sense. The point is that we kept trying things that didn’t work. And of course, that can be frustrating, but what was happening is that Rust was preventing me from going the wrong way. The cool thing about Rust being so strict is that, much like Michelangelo’s statue that already exists within the marble block, it seems that there is just one simple solution waiting to be found. And we did find it. In the end, it was as simple as having a single Item
struct that had the common id
, name
and description
attributes, and a properties
enum that could have different sets of properties (values
for consumable items, usesUntilBreakdown
for tools, etc). It worked, it felt right, it was clean, and it even helped improve the rest of the application code. It just fit. I still appreciate the flexibility and development speed that JavaScript (or any other less strict language) provides, but the absolute certainty that your program will work before you even run it it’s really nice as well (and I don’t think that’s something you can get just by adding types to JavaScript 🙄).
So, history repeated itself indeed, and I managed to create a functional game, learning a lot about Rust in the process. There is a lot of stuff I didn’t explore, like working with threads, but it’s been a great first experience with Rust, and I feel I’ve advanced a lot as a developer getting out of my comfort zone and trying a language so different to everything else I’ve tried before.
Next steps: finish the missing features (like crafting objects and upgrades), play with threads for better time control, and explore WASM to be able to play the game on the browser!
You can find the project here, clone it and play by running cargo run
.
Photo by Dmitry Bayer on Unsplash