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 anfn
.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. EveryFnMut
is alsoFnOnce
.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. EveryFn
is alsoFnMut
andFnOnce
.
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,
}, // ...
];
// ...