Reputation: 471
I'm writing a virtual machine that executes instructions one by one and also can jump forward and backward.
For example, if it has a list of instructions [A, >B, C, D]
and currently is at instruction B
. Jumping forward by 2 will skip 2 instructions (B
and C
) [A, B, C, >D]
. And jumping back by 1 should skip only B
[>A, B, C, D]
.
This is how I implemented the iterator
impl <'a> Iterator for VmReader<'a> {
type Item = &'a Instruction;
fn next(&mut self) -> Option<Self::Item> {
if self.ip < self.chunk.code.len() {
let instruction: &'a Instruction = &self.chunk.code[self.ip];
self.ip += 1;
Some(instruction)
} else {
None
}
}
}
The reader
stores the index of the current instruction and jump_backward(2)
decrements it by 2.
Here's how I'm trying to use it:
while let Some(instruction) = reader.next() {
if instruction == &Instruction::Add {
reader.jump_backward(2);
continue;
}
}
But it does not work since I'm borrowing reader
mutably twice:
error[E0499]: cannot borrow `reader` as mutable more than once at a time
--> src/vmm/vm_reader.rs:99:39
|
99 | while let Some(instruction) = reader.next() {
| ^^^^^^
| |
| second mutable borrow occurs here
| first borrow later used here
100 | if instruction == &Instruction::Add {
101 | reader.jump_backward(2);
| ------ first mutable borrow occurs here
Can you please suggest a solution to this problem?
Upvotes: 1
Views: 315
Reputation: 382194
A solution would be to not use the Iterator
trait but using an iterator like structure which would wrap the ip
cursor but not the reader and whose next
function would take the reader as argument.
It would look somehow like this:
#[derive(Default)]
struct VmIterator {
ip: usize,
}
impl VmIterator {
pub fn next(&mut self, chunk: &Reader) -> Option<&Instruction> {
if self.ip < chunk.code.len() {
let instruction: &'a Instruction = &self.chunk.code[self.ip];
self.ip += 1;
Some(instruction)
} else {
None
}
}
}
This would be safe and clean but would prevent some Iterator patterns.
If you don't need to separate the Reader and the iteration, you may also store directly the ip cursor in the Reader.
Here's an example of this second solution:
#[derive(Debug, Default, Clone)]
struct Instruction {}
struct Reader<'s> {
source: &'s[Instruction],
c: usize,
}
impl<'s> Reader<'s> {
pub fn new(source: &'s[Instruction]) -> Self {
Self { source, c: 0 }
}
pub fn decr(&mut self) {
self.c -= 2;
}
}
impl<'s> Iterator for Reader<'s> {
type Item = &'s Instruction;
fn next(&mut self) -> Option<Self::Item> {
let c = self.c;
if c < self.source.len() {
self.c += 1;
self.source.get(c)
} else {
None
}
}
}
fn main() {
let instructions = vec![Instruction::default(); 10];
let mut r = Reader::new(&instructions);
let mut i = 0;
while let Some(c) = r.next() {
if i%3 == 2 {
r.decr();
}
i += 1;
dbg!(c);
}
}
Note that while it's still an iterator, some Iterator constructs will be forbidden by the borrow checker if you mutate while iterating, hence the explicit while let Some(c) = r.next() {
.
Upvotes: 3