Replace stdio interface with something more sustainable
sunjay opened this issue · comments
The current stdio interface looks something like this:
let mut b: [u8; 5];
stdin.read(b);
stdout.write(b"b = ", b, b"\n");
From a design point of view, this is a pretty simple, magical way of defining IO that relies heavily on the compiler to implement it properly. This design was always meant to be temporary and eventually replaced with something more robust and strongly typed.
After some reflection, I have come to the conclusion that for the sake of backwards compatibility, it would be better to modify these functions to work a bit differently. There will still be some magic for the time being, but for the most part this should be fairly straightforward. The idea with this new design is that it should enable people to write code that will be backwards compatible for the most part. That is, we want to guarantee that at some level it will not require large changes to upgrade this to newer versions of the compiler. I'm being careful with the wording here because there will be some flexibility with breakages, but nothing so severe that it largely impacts people using the compiler. Had the design stayed as it is right now, these problems would be much worse.
Though backwards compatibility and breakages are an important concern, I am okay with some (read: very little) breakage if the compiler can generate a useful error that provides a clear (and hopefully easy) path to upgrade.
New Design: Standard Input
The new design is based on Rust's Read
trait. We're going to be using the Read::read_exact()
interface.
Code like this:
let mut b: [u8; 5];
stdin.read(b);
Will become:
let mut b: [u8; 5];
stdin.read_exact(b);
In the future when references are implemented, this will further become:
let mut b: [u8; 5];
stdin.read_exact(&mut b);
Though that is a small breakage I am willing to accept.
While this API may look very similar to the old one, it has a couple of important differences:
- The old
read()
accepted multiple/variadic arguments. This new version only accepts a single argument - The old
read()
accepted any type. This new version only accepts[u8; N]
- The old
read()
would returnfalse
if the read on any of its arguments failed (giving you no information about what actually failed). This new version will panic if the read fails- The read can fail if there are not enough bytes to read (EOF is reached before reading enough) or if something else more catastrophic happens. See
Read::read_exact()
for more info.. - If you're not familiar with what a panic is, it's a Rust language concept that basically means the program quits with an error. We emulate that in Brainfuck by printing a message and entering an infinite loop.
- Once algebraic datatypes are implemented, this panic will be replaced by a
Result
type which will generate a compiler error if it is ignored, thus making explicit error handling possible - To panic, use the brainfuck feature described here
- The read can fail if there are not enough bytes to read (EOF is reached before reading enough) or if something else more catastrophic happens. See
New Design: Standard Output
For stdout, the change is much simpler. We're just replacing stdout.write
with stdout.print
since that is closer to what the function does and easier to replace in the future.
TODO
- Update examples to use the new API
- Update changelog if necessary
- Update internal implementation of
stdin.read
- Update internal implementation of
stdout.write
tostdout.print
- Update internal implementation of
stdout.writeln
tostdout.println