Mike's blog about random software stuff

Learning the ropes: Rust and Emulators

As a software developer, it’s always fun to learn some new things. For this reason, I decided to start learing the Rust programming language, which seems to be the new cool kid in town. For quite a long time, I disrecarded Rust as yet another “C killer”, a type of language that claims to be the next big thing, but doesn’t really ever get used in anything real. Now that Rust is even getting into the Linux kernel , I guess it’s something I should look into. Not that I believe that Rust is a C killer either. At least I hope so, C is fun!

At the same time, I also was interested in learning a bit about emulators, having an ultimate goal to maybe emulate an NES, or maybe even a PS1 at some point, so as a programming exercise, I decided to create a Chip8 ’emulator’ in C++ and Rust. If you don’t know what Chip8 is, it’s a simple interpreted programming language from the 70’s, designed to simplify the creation of simple games for the computers of that era. The language allows programmers to create stunning games with up to two colors (if you count black and white as colors) and a vast resolution of 64x32 pixels. Because chip8 is so easy to implement, it remains as a popular ’emulator’ that hobbyist-programmers keep making their own versions of, and even new games are created using it.

Starting with something I know

Because I hadn’t programmed Rust beyond a few toy programs and didn’t know how a Chip8 would exactly work, I decided to start by creating the Chip8 implementation in C++. I thought that it might be just too much of a hassel learning a new thing in a completely new language.

So, I did a few google searches and landed on a great blog post on the Chip8 implementation by Tobias V. Langhoff. The Chip8 is somewhat of a hello world to the emulator world, so implementing it in C++ didn’t take too long, and went pretty much as expected. I think I spent a couple nights after my working hours hacking away, and after refreshing my memory on the SFML graphics library, I was done. It was a pretty fun project, as the progress was quite quick, and it only required a few instructions to get the first program rendered on the screen.

IBM logo on the C++ version of the emulator.

From there on, it was mostly just implementing all the different instructions and using some dedicated programs to check that they are working properly.

I used a fairly simple architechture for my program, a class to represent the CPU and another one for the screen and keyboard. I didn’t include much debugging methods or step by step execution in my emulator, as the goal was just to get it working quickly, and I think I managed that well enough for my purposes. Nothing fancy, just a quick’n’dirty build.

Rewriting everything in Rust

Note: I have programmed maybe less than ~1000 lines of code in Rust as I’m writing this, please don’t take anything I write about Rust as a fact.

The main thing Rust seems to be used for currently is rewrting existing C and C++ programs. That’s what I was about to do too now, so it was the time to learn enough Rust to rewrite what I just did in C++.

First, I started by learning how to create a “class” in Rust, as I wanted to follow the same OOP model I used in my C++ program. Rust doesn’t seem to have a difference between classes and structs, so a “class” is just a struct with function implementations along it.

My lack of knownledge about Rust made even creating the first struct reprecenting the “CPU” a bit difficult, as I would have liked it to have fixed sized arrays for things like the memory and the different registers. I never found out if that was possible to do in Rust, so I opted to use a vector for those elements. I guess I would have needed to use something called a slice in my struct and keep the actual ownership of the array somewhere else, but I didn’t like the idea all that much.

My CPU struct ended up looking something like this:

pub struct CPU {
    pc: usize,
    i: u16,
    v: Vec<u8>,
    memory: Vec<u8>,
    stack: Vec<u16>,
    delay_timer: u8,
    sound_timer: u8,
    pub peripherals: Peripherals,
    rnd: ThreadRng,
}

I represented my peripherals, otherwise known as the screen and keyboard as another struct, Peripherals. That was the means of my CPU checking the input states and drawing on the screen.

For the Peripherals implementation, I decided to use a Rust library called piston. It was quite different from the SFML library I used for the C++ implementation, but ended up working nicely.

Learning new stuff always takes it’s time, so at first it was quite the battle trying to remember to cast all your data from type A to type B in cases that seem somewhat trivial from the programming perspective in the C case. I’ll have to admit, it felt quite annoying at first, doing something like returning the first byte of a 16-bit integer required more code stating that “yes, I really want to do this” than the actual operation:

/* in C(++) */
return value & 0xFF;
/* in Rust */
((value & 0xFF)).try_into().unwrap()

In a way it’s nice that Rust really forces you to be aware of all the possible memory issues and type-safety that doesn’t require attention in C, causing hard to find bugs every once in a while, but on the other hand… That’s one ugly line of code for a simple thing.

Another issue related to that, is that overflowing an integer would cause panic in Rust. Again, it’s nice that the language is really making sure you don’t run into things that normally shouldn’t happen, but annoying when the Chip8 implementation is expecting values to overflow. I read that there are compiler flags in the rust compiler that allow overflows, but I opted to just create overflowing add and subtraction functions:

    /*
    Note:
    I'm pretty sure that val & 0xFF == val % (u8::MAX + 1)
    I just don't remember why I did it in two different ways :D
    */
    fn overflow_add(&self, a: u8, b: u8) -> u8 {
        (((a as u16 + b as u16)) % (u8::MAX as u16 + 1)) as u8
    }
    fn overflow_subtract(&self, a: u8, b: u8) -> u8 {
        (((a as i16 - b as i16)) & (0xFF)) as u8
    }

Again. Ugly, but works (I hope, I probably didn’t check that). Also, it most likely is at least partly my fault that the code is looking ugly. I bet there are many of these ‘Rustaceans’ that would cry if they read that line of code. Hopefully I’ll take a look at this after a few years and cringe at myself too. If I don’t, I have either written perfect Rust code in my first program, or I haven’t improved at all in that time. I’m 100% sure I am not writing good Rust right now, and that’s OK. But not improving while trying to… Not so good.

Like in the C++ version, I first implemented just enough instructions to get the IBM logo program running. It took a couple more nights in Rust than it did in C++ because of all the learning going on, but after not too long, I was able to get the first program running:

IBM logo on the Rust version of the emulator.

After that, I just needed to implement all the other instructions, just like before. Quite quick at that point.

Only getting the input required a bit more work with the piston framework. In SFML I was able to poll the keyboard state at any time, so I just did it when I hit an instruction telling me to. But the piston framework worked a bit differently. Its event loop returned button press and release events every time a button was hit. So I ended up storing the button states in a vector that was updated on each key event and read that when the program wanted to poll keys. Not much work in the end, just required reading a bit more of the piston documentation.

Afterthoughts

When I started this project, I had a bit of a bias against Rust. I always (okay, maybe the first few months were a pain years ago) liked programming in C and C++, and was mostly annoyed by the dozens of self-proclaimed C++ killers. And for the first couple of days, I was pretty against how Rust is doing things.

I didn’t like having to tell the compiler multiple times that I think I know what I’m doing, please don’t get in the way. But after doing it for a while, I think I got a bit more used to it and it didn’t bother me that much anymore. After that, I was even starting to appreciate what the compiler was doing. Programming in Rust started to feel quite nice.

Maybe the turning point was when I slowly started to understand at least a little bit of what I was doing. It’s annoying when you know exactly what you want to do, but just don’t know how to express it. After getting past that, I really started to enjoy writing Rust. I definately will be using it for my future weekend/weeknight projects too and learning a lot more about it.

Besides of just the language and it’s syntax, I really do like the Cargo package manager. It’s quite nice that I don’t need to use something like CMake for my Rust projects, which usually takes quite a bit of time to configure, which is away from all the fun stuff.

I’ve also started to write some unit tests even on my own toy projects, sometimes even applying a bit of TDD to my style of writing code. After having enough bugs on my programs I make for fun, I came to the conclusion that writing tests does improve my programming experience, even for programs that no-one will ever use but myself. And even I won’t use them after creating them. It’s always enjoyable to see the row of OK’s from the tests, even if having bugs here and there wouldn’t really matter in the end result at all. That brings me back to Cargo. It made writing those tests really easy. Thumbs up for that too!

Links to the source code for both programs:

C++: https://github.com/salmmike/mpschip8

Rust: https://github.com/salmmike/rustchip8