Home > Back-end >  Initialize method pointer inside struct
Initialize method pointer inside struct

Time:07-06

I am trying to write a NES emulator.

I have created a lookup table that contains all the instructions for the 6502. Each Instruction contains the name of the instruction, a pointer to the method that is the instruction, a pointer to the address mode method, and how many cycles the instruction takes.

These are the structures for the CPU (Nes6502) and a structure for what each Instruction contains.

pub struct Nes6502 {
    bus: Bus,

    /// Accumulator Register
    a: u8,
    /// X Register
    x: u8,
    /// Y Register
    y: u8,
    /// Stack pointer (pointers to a location on the bus)
    stkp: u8,
    /// Program counter
    pc: u16,
    /// Status Register
    status: u8,
    fetched: u8,
    addr_abs: u16,
    addr_rel: u16,
    opcode: u8,
    cycles: u8,
    lookup: Vec<Instruction>,
}

struct Instruction {
    name: String,
    operate: fn() -> u8,
    addrmode: fn() -> u8,
    cycles: u8,
}

But the issue I run into is when I try to initialize the lookup table for the instructions

impl Nes6502 {
    fn new() -> Nes6502 {
        let mut ram= [0; MEM_SIZE]; 
        ram.iter_mut().for_each(|r| *r = 0x00);

        let mut nes = Nes6502 { 
            a: 0x00,
            x: 0x00,
            y: 0x00,
            stkp: 0x00,
            pc: 0x0000,
            status: 0x00,
            fetched: 0x00,
            addr_abs: 0x0000,
            addr_rel: 0x00,
            opcode: 0x00,
            cycles: 0,
            lookup: Vec::new(),

            bus: Bus {
                ram
            }   
        };

        nes.lookup = vec![
            Instruction{ name: String::from("BRK"), operate: || nes.BRK(), addrmode: || nes.IMM(), cycles: 7 } // ...
        ]
    
        nes
    }
    // ...
}

In the part where I initialize the lookup table with all the instructions I get this error

error[E0308]: mismatched types
  --> src/nes_6502.rs:86:63
   |
86 | ...   Instruction{ name: String::from("BRK"), operate: || nes.BRK(), addrmode: || nes.IMM(), cycles: 7 }// ,Instruction{ name: String::fr...
   |                                                        ^^^^^^^^^^^^ expected fn pointer, found closure
   |
   = note: expected fn pointer `fn() -> u8`
                 found closure `[closure@src/nes_6502.rs:86:63: 86:75]`
note: closures can only be coerced to `fn` types if they do not capture any variables
  --> src/nes_6502.rs:86:66
   |
86 | ...   Instruction{ name: String::from("BRK"), operate: || nes.BRK(), addrmode: || nes.IMM(), cycles: 7 }// ,Instruction{ name: String::fr...
   |                                                           ^^^ `nes` captured here

I also tried initializing it like this

nes.lookup = vec![
            Instruction{ name: String::from("BRK"), operate: nes.BRK, addrmode: nes.IMM, cycles: 7 }
           ]

But that gave me this error

error[E0615]: attempted to take value of method `BRK` on type `Nes6502`
  --> src/nes_6502.rs:86:67
   |
86 | ...   Instruction{ name: String::from("BRK"), operate: nes.BRK, addrmode: nes.IMM, cycles: 7 }// ,Instruction{ name: String::from("ORA"),...
   |                                                            ^^^ method, not a field
   |
help: use parentheses to call the method
   |
86 |              Instruction{ name: String::from("BRK"), operate: nes.BRK(), addrmode: nes.IMM, cycles: 7 }

CodePudding user response:

I assume this is your 'minimal' example, as your code doesn't reproduce your issue directly:

struct Bus {
    ram: [i32; 1024],
}

pub struct Nes6502 {
    bus: Bus,

    /// Accumulator Register
    a: u8,
    /// X Register
    x: u8,
    /// Y Register
    y: u8,
    /// Stack pointer (pointers to a location on the bus)
    stkp: u8,
    /// Program counter
    pc: u16,
    /// Status Register
    status: u8,
    fetched: u8,
    addr_abs: u16,
    addr_rel: u16,
    opcode: u8,
    cycles: u8,
    lookup: Vec<Instruction>,
}

struct Instruction {
    name: String,
    operate: fn() -> u8,
    addrmode: fn() -> u8,
    cycles: u8,
}

const MEM_SIZE: usize = 1024;

impl Nes6502 {
    fn new() -> Nes6502 {
        let mut ram = [0; MEM_SIZE];
        ram.iter_mut().for_each(|r| *r = 0x00);

        let mut nes = Nes6502 {
            a: 0x00,
            x: 0x00,
            y: 0x00,
            stkp: 0x00,
            pc: 0x0000,
            status: 0x00,
            fetched: 0x00,
            addr_abs: 0x0000,
            addr_rel: 0x00,
            opcode: 0x00,
            cycles: 0,
            lookup: Vec::new(),

            bus: Bus { ram },
        };

        nes.lookup = vec![
            Instruction {
                name: String::from("BRK"),
                operate: || nes.BRK(),
                addrmode: || nes.IMM(),
                cycles: 7,
            }, // ...
        ];

        nes
    }

    fn BRK(&self) -> u8 {
        todo!()
    }
    fn IMM(&self) -> u8 {
        todo!()
    }
    // ...
}
error[E0308]: mismatched types
  --> src/lib.rs:62:26
   |
62 |                 operate: || nes.BRK(),
   |                          ^^^^^^^^^^^^ expected fn pointer, found closure
   |
   = note: expected fn pointer `fn() -> u8`
                 found closure `[closure@src/lib.rs:62:26: 62:38]`
note: closures can only be coerced to `fn` types if they do not capture any variables
  --> src/lib.rs:62:29
   |
62 |                 operate: || nes.BRK(),
   |                             ^^^ `nes` captured here

error[E0308]: mismatched types
  --> src/lib.rs:63:27
   |
63 |                 addrmode: || nes.IMM(),
   |                           ^^^^^^^^^^^^ expected fn pointer, found closure
   |
   = note: expected fn pointer `fn() -> u8`
                 found closure `[closure@src/lib.rs:63:27: 63:39]`
note: closures can only be coerced to `fn` types if they do not capture any variables
  --> src/lib.rs:63:30
   |
63 |                 addrmode: || nes.IMM(),
   |                              ^^^ `nes` captured here

fn vs Fn/FnMut/FnOnce

  • fn is a function pointer. It is simply an address that points to some executable function. No data can be attached to an fn.
  • FnOnce is a trait that describes some object that can be executed at least once. The object gets consumed in the process of executing it.
  • FnMut is a trait that describes some object that can be executed multiple times, but either has a side effect or changes its internal state in the process. Mutable access to the object is required to execute it. Every FnMut is also FnOnce.
  • Fn is a trait that describes some object that can be executed many times without side effects or internal state changes. A simple immutable reference is required to execute it. Every Fn is also FnMut and FnOnce.

Fn, FnMut and FnOnce are all based on actual objects in memory, and not just pointers. Therefore those can carry data.

Why is this important for your case?

|| nes.BRK() is a closure that takes no argument, but uses the nes object. Therefore it needs to store the reference to the nes object somewhere.

So the first intuition would be to change fn to Box<dyn Fn>, like this:

struct Bus {
    ram: [i32; 1024],
}

pub struct Nes6502 {
    bus: Bus,

    /// Accumulator Register
    a: u8,
    /// X Register
    x: u8,
    /// Y Register
    y: u8,
    /// Stack pointer (pointers to a location on the bus)
    stkp: u8,
    /// Program counter
    pc: u16,
    /// Status Register
    status: u8,
    fetched: u8,
    addr_abs: u16,
    addr_rel: u16,
    opcode: u8,
    cycles: u8,
    lookup: Vec<Instruction>,
}

struct Instruction {
    name: String,
    operate: Box<dyn Fn() -> u8>,
    addrmode: Box<dyn Fn() -> u8>,
    cycles: u8,
}

const MEM_SIZE: usize = 1024;

impl Nes6502 {
    fn new() -> Nes6502 {
        let mut ram = [0; MEM_SIZE];
        ram.iter_mut().for_each(|r| *r = 0x00);

        let mut nes = Nes6502 {
            a: 0x00,
            x: 0x00,
            y: 0x00,
            stkp: 0x00,
            pc: 0x0000,
            status: 0x00,
            fetched: 0x00,
            addr_abs: 0x0000,
            addr_rel: 0x00,
            opcode: 0x00,
            cycles: 0,
            lookup: Vec::new(),

            bus: Bus { ram },
        };

        nes.lookup = vec![
            Instruction {
                name: String::from("BRK"),
                operate: Box::new(|| nes.BRK()),
                addrmode: Box::new(|| nes.IMM()),
                cycles: 7,
            }, // ...
        ];

        nes
    }

    fn BRK(&self) -> u8 {
        todo!()
    }
    fn IMM(&self) -> u8 {
        todo!()
    }
    // ...
}
error[E0506]: cannot assign to `nes.lookup` because it is borrowed
  --> src/lib.rs:59:9
   |
59 |         nes.lookup = vec![
   |         ^^^^^^^^^^ assignment to borrowed `nes.lookup` occurs here
...
62 |                 operate: Box::new(|| nes.BRK()),
   |                          ----------------------
   |                          |        |  |
   |                          |        |  borrow occurs due to use in closure
   |                          |        borrow of `nes.lookup` occurs here
   |                          cast requires that `nes` is borrowed for `'static`

error[E0597]: `nes` does not live long enough
  --> src/lib.rs:62:38
   |
62 |                 operate: Box::new(|| nes.BRK()),
   |                          ------------^^^-------
   |                          |        |  |
   |                          |        |  borrowed value does not live long enough
   |                          |        value captured here
   |                          cast requires that `nes` is borrowed for `'static`
...
69 |     }
   |     - `nes` dropped here while still borrowed

error[E0597]: `nes` does not live long enough
  --> src/lib.rs:63:39
   |
63 |                 addrmode: Box::new(|| nes.IMM()),
   |                           ------------^^^-------
   |                           |        |  |
   |                           |        |  borrowed value does not live long enough
   |                           |        value captured here
   |                           cast requires that `nes` is borrowed for `'static`
...
69 |     }
   |     - `nes` dropped here while still borrowed

error[E0505]: cannot move out of `nes` because it is borrowed
  --> src/lib.rs:68:9
   |
62 |                 operate: Box::new(|| nes.BRK()),
   |                          ----------------------
   |                          |        |  |
   |                          |        |  borrow occurs due to use in closure
   |                          |        borrow of `nes` occurs here
   |                          cast requires that `nes` is borrowed for `'static`
...
68 |         nes
   |         ^^^ move out of `nes` occurs here

This brings us to the next problem though: The closure outlives the current function, and therefore the compiler cannot guarantee that the nes object lives longer than the closure. Further, for as long as the closure borrows the nes object, no mutable reference to the nes can exist, which is probably not what you want. I'm sure you want to modify the nes object.

So what you really want is to only borrow the nes object while executing the closure. This can be achieved by passing the nes object into the closure as a function argument.

Once we have done that, the closure now no longer stores any references or objects, and we can use a plain fn function pointer again:

struct Bus {
    ram: [i32; 1024],
}

pub struct Nes6502 {
    bus: Bus,

    /// Accumulator Register
    a: u8,
    /// X Register
    x: u8,
    /// Y Register
    y: u8,
    /// Stack pointer (pointers to a location on the bus)
    stkp: u8,
    /// Program counter
    pc: u16,
    /// Status Register
    status: u8,
    fetched: u8,
    addr_abs: u16,
    addr_rel: u16,
    opcode: u8,
    cycles: u8,
    lookup: Vec<Instruction>,
}

struct Instruction {
    name: String,
    operate: fn(&mut Nes6502) -> u8,
    addrmode: fn(&Nes6502) -> u8,
    cycles: u8,
}

const MEM_SIZE: usize = 1024;

impl Nes6502 {
    fn new() -> Nes6502 {
        let mut ram = [0; MEM_SIZE];
        ram.iter_mut().for_each(|r| *r = 0x00);

        let mut nes = Nes6502 {
            a: 0x00,
            x: 0x00,
            y: 0x00,
            stkp: 0x00,
            pc: 0x0000,
            status: 0x00,
            fetched: 0x00,
            addr_abs: 0x0000,
            addr_rel: 0x00,
            opcode: 0x00,
            cycles: 0,
            lookup: Vec::new(),

            bus: Bus { ram },
        };

        nes.lookup = vec![
            Instruction {
                name: String::from("BRK"),
                operate: |nes| nes.BRK(),
                addrmode: |nes| nes.IMM(),
                cycles: 7,
            }, // ...
        ];

        nes
    }

    fn BRK(&self) -> u8 {
        todo!()
    }
    fn IMM(&self) -> u8 {
        todo!()
    }
    // ...
}

Another minor nitpick

The name of your Instruction is most likely already known at compile time, so it saves time and resources to use &'static str instead:

struct Instruction {
    name: &'static str,
    operate: fn(&mut Nes6502) -> u8,
    addrmode: fn(&Nes6502) -> u8,
    cycles: u8,
}

// ...

        nes.lookup = vec![
            Instruction {
                name: "BRK",
                operate: |nes| nes.BRK(),
                addrmode: |nes| nes.IMM(),
                cycles: 7,
            }, // ...
        ];

// ...
  • Related