4
\$\begingroup\$

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<In, Out> {
    pub memory: Vec<u8>, 
    pub pointer: usize, 
    input: In, 
    output: Out,
}

impl<In: Read, Out: Write> Machine<In, Out> {
    pub fn new(input: In, output: Out) -> Machine<In, Out> {
        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:

  1. 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?

  2. 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 mutable
    

    Can I write the same test without having to nest scopes? Is so, how?

Any other comment, hint or advice will be very welcome!

\$\endgroup\$
3
\$\begingroup\$

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<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);
}
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.