Io v0.1.0
Installation
Add Io to the [Dependencies] table of your package's Rux.toml:
[Dependencies]
Io = { Namespace = "Rux", Version = "0.1.0" }
Standard packages are declared by hand: the registry does not serve them, so rux add Rux/Io cannot add them.
Dependencies
Allocator0.1.0Core0.1.0Format0.1.0FreeBSD0.1.0 (FreeBSD only)Linux0.1.0 (Linux only)macOS0.1.0 (macOS only)Memory0.1.0Text0.1.0Windows0.1.0 (Windows only)
Guide
Streams, console I/O, readers and writers.
Console access is written directly against each platform's own primitives, selected at compile time: the write/read syscalls on Linux, macOS, and the BSDs, and WriteFile/ReadFile on Windows. There is no C runtime in the path.
What it provides
| Function | Purpose |
|---|---|
Print | Write a value to standard output |
PrintLine | Write a value followed by a newline |
ReadLine | Append one line from standard input |
ReadLineFrom | Append one line from any reader, byte by byte |
ReadExact | Fill a byte slice from any reader |
WriteAll | Send a byte slice to any writer |
ReadTextLine | Append one validated line from a buffered reader |
Print and PrintLine accept values implementing Display from Rux/Text, along with their primitive and text overloads. The primitive overloads cover every boolean width from bool8 to bool512, the floats float8, float16, float32 and float64, the integers up to 64 bits with int and uint, and every character width from char8 to char512. float80 and wider have no overload, because their text allocates: render one through Rux/Format's Float80 to Float512 values with FromValue and FormatWith, passing the allocator. They return the failure of the write as an IoError? — none when everything was written — rather than a fallible, so a bare PrintLine("ready"); statement needs no handling while a caller who cares writes if PrintLine(report) is IoError { ... }.
Given arguments after it, the first text is a pattern: PrintLine("{} of {}", done, total) fills each {} or {:spec} placeholder from the next argument, and {{ and }} write a literal brace. The pattern parameter is marked #Format(), so when it is a string literal the compiler counts its placeholders against the arguments, and PrintLine("{} {}", 1) is a compile error rather than a silent failure at run time. A pattern known only at run time that does not match reports InvalidInput through the returned IoError? and prints nothing. With no arguments the text is not a pattern at all: PrintLine("{{}}") writes {{}} verbatim, braces and all.
Both line readers hold the same contract: a line feed ends a line, a carriage return immediately before it belongs to the ending, the last line needs no ending, and the line must be UTF-8. A line that is not is InvalidText, reported once the line has been consumed through its ending, so the next call starts on the next line. ReadLine and ReadLineFrom read one byte at a time and never take a byte past the line, which is right for standard input or any stream shared with whatever reads next; ReadTextLine reads through a BufferedReader and also bounds the line's length.
Every stream operation that can fail returns a native fallible with an IoError failure: Read succeeds with the number of bytes read, Write with the number taken, Seek with the new position, and Flush, ReadExact, WriteAll, and the line readers with (). The end of a stream is the EndOfStream failure, which IsEnd recognizes, and IsTransient names the interruptions worth retrying. An IoError renders through {} as its kind's words and the platform's code, as not found (os error 2).
Streams in memory
ByteReader reads a slice of bytes the caller holds and ByteWriter gathers what is written into a block that grows as needed, so code written against Reader and Writer can run without a file or a socket. Both seek within what they hold: a seek back in a ByteWriter overwrites in place, and a seek past the end is refused with InvalidInput. A ByteReader ends with EndOfStream like any other stream.
Text and streams
Display writes into a TextWriter and a stream takes bytes, so something has to stand between them. ConsoleWriter is that adapter over standard output and StreamWriter is it over any Writer — a file, a buffered writer, a pipe — which is what lets a value describe itself anywhere other than standard output or memory:
import Core::Failed;
import Io::{ IoError, StandardOut, StreamWriter, WriteValueLine };
import Text::TextWriter;
func Main() -> int {
var out = StandardOut::Acquire();
let measurement: int32 = 42;
// The adapter turns a stream into a text writer. It cannot return an I/O error through the text protocol, so
// it keeps the first one it saw in `failure` — which is why that is checked afterwards rather than at the call.
var failure: IoError? = none;
var adapter = StreamWriter(out, @failure);
let text: &var TextWriter = adapter;
// The type argument is required: the function is generic over the value so that it can borrow it rather than
// take ownership, and a generic bound is not inferred from an interface value.
let written = WriteValueLine<int32>(text, measurement);
if failure is IoError || Failed(written) {
return 1;
}
return 0;
}
Neither error type can hold the other's failures, so the crossing is made explicitly in both directions. FromIoError turns what a stream reported into what a formatter understands: success stays success and every failure becomes WriterFailure, which is all FormatError can say about a destination. That is lossy on purpose, and the adapter makes the loss good by recording the original IoError in an optional slot the caller holds, none until something fails — the formatter learns the one thing it can act on, and the caller learns exactly what happened. Once a failure is recorded the stream is not touched again, so a value part-way through rendering stops rather than writing into a destination that has already refused.
ToIoError comes back the other way, and the distinction it preserves is the one that decides what to do next: a request that was wrong is InvalidInput and will be wrong again, while a destination that failed came back as an IoError in the first place. Bytes that were not text are InvalidText; a bounded destination that filled is LimitExceeded. Nothing is stuffed into IoError's raw, which is errno or the Win32 code and is meant to be looked up; a caller who needs to know which formatting failure it was builds a writer and calls Text::WriteValue themselves.
WriteValueLine and WriteFormatLine write a value or a pattern followed by a newline, and write the newline only if what came before it went out whole. A line is a value and its terminator together, so a value that could not be written must not be followed by one. Every PrintLine overload follows the same rule. WriteValueLine takes its value by bound rather than as an interface value, so the value is borrowed rather than given up: binding a move-only value to a Display transfers ownership of it, which would let such a value be written exactly once.
Stream helpers borrow concrete implementations directly as &var Reader or &var Writer interface views, and a named stream passed straight into one of them borrows without any binding: WriteAll(file, contents)?. The borrow neither copies nor consumes the stream, and it ends at the call. BufferedReader and BufferedWriter must keep their streams after construction, so they store ordinary interface handles; a stream that cannot be stored that way — because binding it would move it — supplies a stored adapter of its own, such as FileSystem::FileStream, whose representation is private and whose lifetime contract is documented where it lives. BufferedReader::New and BufferedWriter::New fail with Other when their buffer cannot be allocated. Both buffered types prohibit copying, move with <-, and release their owned buffers through ~BufferedReader and ~BufferedWriter.
Example
import Allocator::{ Allocator, SystemAllocator };
import Io::{ PrintLine, ReadLine };
import Text::StringBuilder;
func Main() -> int {
var system = SystemAllocator();
let allocator: Allocator = system;
var name = StringBuilder(allocator);
PrintLine("What is your name?");
ReadLine(name) catch { else => return 1 };
let text = name.IntoString();
PrintLine(text);
return 0;
}
Index
Interfaces
| Name | Summary |
|---|---|
Reader | Somewhere bytes come from. |
Writer | Somewhere bytes go. |
Seeker | A stream whose position can be moved. |
Types
| Name | Summary |
|---|---|
Endian | A byte order: which end of a multi-byte value comes first. |
BufferedReader | A reader that fills a buffer in large reads and serves small ones from it. |
BufferedWriter | A writer that gathers small writes into a buffer and sends it in one piece. |
IoErrorKind | What an I/O operation reported. |
IoError | A structured I/O failure: the shared meaning, and the platform's own code beside it. |
ByteReader | A reader of a slice of bytes, from its start. |
ByteWriter | A writer that keeps what is written in a growing block of memory. |
StandardIn | Standard input, with the console-versus-redirection question answered at acquisition. |
StandardOut | Standard output: a byte Writer whose encoding matches where the bytes land. |
StandardError | Standard error: the same contract as standard output, on the stream reports belong to. |
SeekFrom | Where a seek starts counting from. |
ConsoleWriter | A TextWriter over standard output that records the stream's failure where the caller can see it. |
StreamWriter | A TextWriter over any stream, recording the stream's failure where the caller can see it. |
Functions
| Name | Summary |
|---|---|
ReadWord | Reads a width-byte unsigned word in the given order, into the low bytes of the result. |
WriteWord | Writes the low width bytes of value in the given order. |
ReadUint8 | Reads a uint8 in the given order. |
WriteUint8 | Writes a uint8 in the given order. |
ReadInt8 | Reads an int8: the unsigned bytes, reinterpreted in two's complement. |
WriteInt8 | Writes an int8: its two's-complement bytes, unchanged. |
ReadUint16 | Reads a uint16 in the given order. |
WriteUint16 | Writes a uint16 in the given order. |
ReadInt16 | Reads an int16: the unsigned bytes, reinterpreted in two's complement. |
WriteInt16 | Writes an int16: its two's-complement bytes, unchanged. |
ReadUint32 | Reads a uint32 in the given order. |
WriteUint32 | Writes a uint32 in the given order. |
ReadInt32 | Reads an int32: the unsigned bytes, reinterpreted in two's complement. |
WriteInt32 | Writes an int32: its two's-complement bytes, unchanged. |
ReadUint64 | Reads a uint64 in the given order. |
WriteUint64 | Writes a uint64 in the given order. |
ReadInt64 | Reads an int64: the unsigned bytes, reinterpreted in two's complement. |
WriteInt64 | Writes an int64: its two's-complement bytes, unchanged. |
ReadUint128 | Reads a uint128 in the given order. |
WriteUint128 | Writes a uint128 in the given order. |
ReadInt128 | Reads an int128: the unsigned bytes, reinterpreted in two's complement. |
WriteInt128 | Writes an int128: its two's-complement bytes, unchanged. |
ReadUint256 | Reads a uint256 in the given order. |
WriteUint256 | Writes a uint256 in the given order. |
ReadInt256 | Reads an int256: the unsigned bytes, reinterpreted in two's complement. |
WriteInt256 | Writes an int256: its two's-complement bytes, unchanged. |
ReadUint512 | Reads a uint512 in the given order. |
WriteUint512 | Writes a uint512 in the given order. |
ReadInt512 | Reads an int512: the unsigned bytes, reinterpreted in two's complement. |
WriteInt512 | Writes an int512: its two's-complement bytes, unchanged. |
ReadFloat32 | Reads a float32 as the IEEE 754 bit pattern of its width. |
WriteFloat32 | Writes a float32 as the IEEE 754 bit pattern of its width. |
ReadFloat64 | Reads a float64 as the IEEE 754 bit pattern of its width. |
WriteFloat64 | Writes a float64 as the IEEE 754 bit pattern of its width. |
PrintValue | Writes a value's text to standard output. |
PrintValueLine | Writes a value's text to standard output, then a newline. |
Print | Writes a value's text to standard output. |
PrintLine | Writes a value's text to standard output, then a newline. |
ReadLine | Appends the next line of standard input to builder, without its ending. |
ReadLineFrom | Appends the next line of reader to builder, without its ending, reading one byte at a time. |
ReadExact | Fills all of into, looping over partial reads, or says why it could not. |
WriteAll | Writes all of bytes, looping over partial writes, or says why it could not. |
FromIoError | Turns what a stream reported into what a formatter understands. |
ToIoError | Turns a formatting failure into something a stream's caller can act on. |
WriteValueLine<T> | Writes a value's text, then a newline, and writes the newline only if the value went out whole. |
WriteFormatLine | Writes format with each {} replaced by the next argument, then a newline, on the same rule. |
ReadTextLine | Appends the next line of reader to builder, without its ending. |
Constants
| Name | Summary |
|---|---|
DefaultBufferCapacity | The room a buffered stream takes when the caller expresses no preference. |
ZeroProgressLimit | How many consecutive zero-progress rounds the looping helpers tolerate before refusing. |