patternrustMinor
Input/output for a simple Brainfuck interpreter
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
The code implements just the input and output routines for the interpreter. The
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
-
In the test function
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
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
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:
We are attempting to immutably borrow something that's already borrowed. The compiler is kind enough to show us where the borrow occurs:
And where it ends:
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
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.