0dmg

Learning Rust by building a partial Game Boy emulator.

This project is maintained by jeremyBanks

Digesting Exploring Rust Fat Pointers

So in C++, for any class that has implemented abstract/virtual methods, every instance of that class includes a pointer to a “vtable”, which maps the abstract function identifier to the actual implementation in memory. This is how you can have a use pointer to a *Printable object, without knowing its concrete type: you go into its vtable, and look up the function pointer.

This is very similar to a prototype, with a key distinction being that it only exists if it’s required. (If the compiler statically knows what the type will be, it doesn’t need a vtable loopup, and it won’t be given one.)

This is mostly just an implementation details, but apparently in Rust, instead of including the vtable pointer in every struct (oh no, the overhead!), it’s included a part of the pointer. Instead of just having the address of the instance in memory, the pointer also has the address of its vtable.

But these “fat pointers” are only used in cases where you need dynamic/generic dispatch. So if you use the same class somewhere it can be statically analyzed, it will just use a convention thin pointer. It’s interesting to see learn how this is implemented in a more static/compiled language, after so much type using more dynamic languages.

Exploring Immutability

With thanks to the Stack Overflow Rust chat room

At first I thought that let versus let mut in Rust was just like const versus let in JavaScript, but it’s actually deeper: if you have an immutable reference to an object, you can’t modify fields or call any methods that modify fields. An immutable reference enforces strictly-functional use of the data, deeply.

Today, I wanted to create a struct field whose binding and contents couldn’t be modified, even if you had a mutable reference to the struct (because lots of my code needs a mutable reference to the struct, but none of it needs to modify this data). Specifically, I had a struct CPU {} with a field rom: Vec<u8>. It doesn’t make sense to write to ROM, so I wanted to make the Vector (mutable/resizable array/list) immutable.

In TypeScript or C#, I’d start by applying the readonly attribute to the field, so the binding/reference would only be mutable by the constructor. But it turns out that Rust doesn’t have this feature. It’s structs seem simpler, and don’t support anything beyond pub and not-pub. If you have a mut reference to a struct, you can write any fields you can read.

The common solution is to make the field private, with a “getter” that returns a read-only to the field. This would prevent the field binding or contents from being changed externally. Worth considering.

I wanted to also prevent the field’s vector from being modified by other methods from the same type. They wouldn’t be affected by making it private. And it looks like it isn’t possible to prevent methods from changing the field binding to refer to a different vector. :-/

However, it is possible to make the vector itself immutable! So in our case: a method will be able to change the rom field to point to a different vector, but it won’t be able to modify the contents of any of those vectors.

We accomplish this by defining a wrapper “tuple struct” type, which keeps the field private, but provides a method to borrow an immutable reference to it:

fn main() {
    let cpu = emulator::Cpu::new();
    {
        // We can read the Vec, but we can't mutate it.
        let rom = cpu.rom.borrow();
        println!("{:?}", rom);
    }
}

mod emulator {
    // Creates a wrapper type for Vec<u8>.
    // The wrapped value is private to this module.
    pub struct Rom(Vec<u8>);

    impl Rom {
        // Returns an immutable reference to our wrapped Vec.
        pub fn borrow(&self) -> &[u8] {
            return &self.0;
        }

        // If you want to allow new Rom instances to be defined
        // outside of this module, you will also need a fn new().
    }

    pub struct Cpu {
        pub rom: Rom,
    }

    impl Cpu {
        pub fn new() -> Self {
            Cpu {
                rom: Rom(vec![0, 1, 2])
            }
        }
    }
}

In the end, I decided not to commit any changes like this. It’s not an bad idea, but the code is still very fluid, and I’m still uncertain enough about it and the language that I’ll prefer to keep it simple.

Hopefully, I will have the sense not to write to the contents of a field named rom.