HiveBrain v1.2.0
Get Started
← Back to all entries
patternrustMinor

Input/output for a simple Brainfuck interpreter

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
interpretersimpleoutputinputforbrainfuck

Problem

The following code comes from a simple Brainfuck interpreter I'm working on to learn the Rust language. Error handling is omitted for simplicity. It is tested on rustc version 1.1.0-dev (af522079a 2015-05-14) (built 2015-05-14).

The code implements just the input and output routines for the interpreter. The read_cell method reads a single byte from the Read object input and stores it in the memory cell at pointer. write_cell writes the byte a the memory cell at pointer to the Write object output.

use std::io::Read;
use std::io::Write;

pub struct Machine {
    pub memory: Vec, 
    pub pointer: usize, 
    input: In, 
    output: Out,
}

impl Machine {
    pub fn new(input: In, output: Out) -> Machine {
        Machine { 
            memory: vec![0; 2], 
            pointer: 0, 
            input: input, 
            output: output 
        }
    }

    pub fn read_cell(&mut self) {
        self.input.read(&mut self.memory[self.pointer..self.pointer+1]).unwrap();
    }

    pub fn write_cell(&mut self) {
        self.output.write(&self.memory[self.pointer..self.pointer+1]).unwrap();
    }
}

#[cfg(test)]
mod test {

    use super::*;

    #[test]
    fn test_write_cell() {
        let input = "".as_bytes();
        let mut output = vec![];
        {
            let mut machine = Machine::new(input, &mut output);
            machine.memory[machine.pointer] = 1;
            machine.write_cell();
        }
        assert_eq!(vec![1], output);
    }
}


There are two main points in the code that are concerning me:

-
I cannot find in the standard library API an obvious method to read or write a single byte. There is a better/clearer way to implement the read_cell and write_cell methods?

-
In the test function test_write_cell I had to declare machine inside a nested scope. If it is declared in the same scope of output I get this error at the assert_eq! line:

cannot borrow output` as immutable because it is also borrowed as

Solution

a better/clearer way to implement the read_cell and write_cell methods?

What you have seems pretty good to me. Rust's IO methods rely on having a buffer to read into. In many cases, you might see a single byte read with something like

let mut buf = [0; 1];
reader.read(&mut buf).unwrap();


In your case, you already have a buffer, so you might as well read directly into it.


Can I write the same test without having to nest scopes?

Nope. Let's comment out the nested braces and look at the error messages:

error: cannot borrow output as immutable because it is also
borrowed as mutable

match ( & ( $ left ) , & ( $ right ) ) {
^~~~~~~~~~~
note: in expansion of assert_eq!


We are attempting to immutably borrow something that's already borrowed. The compiler is kind enough to show us where the borrow occurs:

note: previous borrow of output occurs here; the mutable borrow
prevents subsequent moves, borrows, or modification of
output until the borrow ends

let mut machine = Machine::new(input, &mut output);
^~~~~~


And where it ends:

note: previous borrow ends here

fn test_write_cell() {
...
}
^


When there is an outstanding mutable borrow, Rust only that borrow to exist. This removes whole classes of bugs around "spooky action at a distance" as well as potential data races. The extra braces provide a lifetime that the borrow can live during; once the block is exited, the borrow is no longer needed.

The only solution I know of is to use what I like to call an exploder. If you are familiar with constructors and destructors, then an exploder is like a destructor that returns values. There's a common pattern in the standard library with methods called into_inner. Here's how it could look for your code:

impl Machine {
    pub fn into_inner(self) -> (In, Out) {
        (self.input, self.output)
    }
}

fn test_write_cell() {
    let input = "".as_bytes();
    let output = vec![];

    let mut machine = Machine::new(input, output);
    machine.memory[machine.pointer] = 1;
    machine.write_cell();

    let (_, output) = machine.into_inner();

    assert_eq!(vec![1], output);
}

Code Snippets

impl<In: Read, Out: Write> Machine<In, Out> {
    pub fn into_inner(self) -> (In, Out) {
        (self.input, self.output)
    }
}

fn test_write_cell() {
    let input = "".as_bytes();
    let output = vec![];

    let mut machine = Machine::new(input, output);
    machine.memory[machine.pointer] = 1;
    machine.write_cell();

    let (_, output) = machine.into_inner();

    assert_eq!(vec![1], output);
}

Context

StackExchange Code Review Q#90645, answer score: 3

Revisions (0)

No revisions yet.