Synchronous I/O. Synchronous I/O. Synchronous I/O
This module defines the Rust interface for synchronous I/O.
It models byte-oriented input and output with the Reader and Writer traits.
Types that implement both Reader
and Writer
and called 'streams',
and automatically implement trait Stream
.
Implementations are provided for common I/O streams like
file, TCP, UDP, Unix domain sockets.
Readers and Writers may be composed to add capabilities like string
parsing, encoding, and compression.
This will likely live in std::io, not std::rt::io.
Some examples of obvious things you might want to do
Read lines from stdin
for stdin().each_line |line| { println(line) }
Read a complete file to a string, (converting newlines?)
let contents = File::open("message.txt").read_to_str(); // read_to_str??
Write a line to a file
let file = File::open("message.txt", Create, Write); file.write_line("hello, file!");
Iterate over the lines of a file
do File::open("message.txt").each_line |line| { println(line) }
Pull the lines of a file into a vector of strings
let lines = File::open("message.txt").line_iter().to_vec();
Make an simple HTTP request
let socket = TcpStream::open("localhost:8080"); socket.write_line("GET / HTTP/1.0"); socket.write_line(""); let response = socket.read_to_end();
Connect based on URL? Requires thinking about where the URL type lives
and how to make protocol handlers extensible, e.g. the "tcp" protocol
yields a TcpStream
.
connect("tcp://localhost:8080");
streams
.When discussing I/O you often hear the terms 'synchronous' and 'asynchronous', along with 'blocking' and 'non-blocking' compared and contrasted. A synchronous I/O interface performs each I/O operation to completion before proceeding to the next. Synchronous interfaces are usually used in imperative style as a sequence of commands. An asynchronous interface allows multiple I/O requests to be issued simultaneously, without waiting for each to complete before proceeding to the next.
Asynchronous interfaces are used to achieve 'non-blocking' I/O. In traditional single-threaded systems, performing a synchronous I/O operation means that the program stops all activity (it 'blocks') until the I/O is complete. Blocking is bad for performance when there are other computations that could be done.
Asynchronous interfaces are most often associated with the callback (continuation-passing) style popularised by node.js. Such systems rely on all computations being run inside an event loop which maintains a list of all pending I/O events; when one completes the registered callback is run and the code that made the I/O request continues. Such interfaces achieve non-blocking at the expense of being more difficult to reason about.
Rust's I/O interface is synchronous - easy to read - and non-blocking by default.
Remember that Rust tasks are 'green threads', lightweight threads that are multiplexed onto a single operating system thread. If that system thread blocks then no other task may proceed. Rust tasks are relatively cheap to create, so as long as other tasks are free to execute then non-blocking code may be written by simply creating a new task.
When discussing blocking in regards to Rust's I/O model, we are
concerned with whether performing I/O blocks other Rust tasks from
proceeding. In other words, when a task calls read
, it must then
wait (or 'sleep', or 'block') until the call to read
is complete.
During this time, other tasks may or may not be executed, depending on
how read
is implemented.
Rust's default I/O implementation is non-blocking; by cooperating directly with the task scheduler it arranges to never block progress of other tasks. Under the hood, Rust uses asynchronous I/O via a per-scheduler (and hence per-thread) event loop. Synchronous I/O requests are implemented by descheduling the running task and performing an asynchronous request; the task is only resumed once the asynchronous request completes.
For blocking (but possibly more efficient) implementations, look
in the io::native
module.
I/O is an area where nearly every operation can result in unexpected errors. It should allow errors to be handled efficiently. It needs to be convenient to use I/O when you don't care about dealing with specific errors.
Rust's I/O employs a combination of techniques to reduce boilerplate while still providing feedback about errors. The basic strategy:
io_error
condition which provides an opportunity to inspect
an IoError object containing details.Option
, an empty
vector, or other designated error value.Option
, e.g. impl<R: Reader> Reader for Option<R>
,
so that nullable values do not have to be 'unwrapped' before use.These features combine in the API to allow for expressions like
File::new("diary.txt").write_line("met a girl")
without having to
worry about whether "diary.txt" exists or whether the write
succeeds. As written, if either new
or write_line
encounters
an error the task will fail.
If you wanted to handle the error though you might write
let mut error = None;
do io_error::cond(|e: IoError| {
error = Some(e);
}).in {
File::new("diary.txt").write_line("met a girl");
}
if error.is_some() {
println("failed to write my diary");
}
XXX: Need better condition handling syntax
In this case the condition handler will have the opportunity to
inspect the IoError raised by either the call to new
or the call to
write_line
, but then execution will continue.
So what actually happens if new
encounters an error? To understand
that it's important to know that what new
returns is not a File
but an Option<File>
. If the file does not open, and the condition
is handled, then new
will simply return None
. Because there is an
implementation of Writer
(the trait required ultimately required for
types to implement write_line
) there is no need to inspect or unwrap
the Option<File>
and we simply call write_line
on it. If new
returned a None
then the followup call to write_line
will also
raise an error.
This structure will encourage a programming style that is prone to errors similar to null pointer dereferences. In particular code written to ignore errors and expect conditions to be unhandled will start passing around null or zero objects when wrapped in a condition handler.
close
vs. RAIIIn scope for core
Some I/O things don't belong in core
fn connect
Out of scope
Path
or &str
? Path
makes simple cases verbose.
Overloading would be nice.open
methodclose
at all? dtors might be good enoughpub use self::stdio::stdin; |
pub use self::stdio::stdout; |
pub use self::file::FileStream; |
pub use self::timer::Timer; |
pub use self::extensions::ReaderUtil; |
pub use self::net::ip::IpAddr; |
pub use self::extensions::WriterByteConversions; |
pub use self::pipe::UnboundPipeStream; |
pub use self::net::tcp::TcpListener; |
pub use self::stdio::stderr; |
pub use self::net::tcp::TcpStream; |
pub use self::stdio::println; |
pub use self::net::udp::UdpStream; |
pub use self::extensions::ReaderByteConversions; |
pub use self::pipe::PipeStream; |
pub use self::process::Process; |
pub use self::stdio::print; |
buffered | Buffered I/O wrappers Buffered I/O wrappers Buffering wrappers for I/O traits |
comm_adapters | Interop between byte streams and pipes. Not sure where it belongs Interop between byte streams and pipes. Not sure where it belongs |
extensions | Extension traits Extension traits Utility mixins that apply to all Readers and Writers |
file | Synchronous, non-blocking file I/O. Synchronous, non-blocking file I/O. Synchronous File I/O |
flate | Basic stream compression. XXX: Belongs with other flate code Basic stream compression. XXX: Belongs with other flate code Some various other I/O types |
io_error | |
mem | Readers and Writers for memory buffers and strings. Readers and Writers for memory buffers and strings. Readers and Writers for in-memory buffers |
mock | Mock implementations for testing Mock implementations for testing |
native | Thread-blocking implementations |
net | Synchronous, non-blocking network I/O. Synchronous, non-blocking network I/O. |
option | Implementations for Option Implementations for Option Implementations of I/O traits for the Option type |
pipe | Synchronous, in-memory I/O. Synchronous, in-memory I/O. Synchronous, in-memory pipes. |
process | Child process management. Child process management. Bindings for executing child processes |
read_error | |
stdio | Non-blocking access to stdin, stdout, stderr Non-blocking access to stdin, stdout, stderr |
support | Non-I/O things needed by the I/O module Non-I/O things needed by the I/O module |
timer | Basic Timer Basic Timer |
FileStat | |
IoError | The type passed to I/O condition handlers to indicate error |
FileAccess | Access permissions with which the file should be opened.
|
FileMode | Instructions on how to open a file and return a |
IoErrorKind | |
SeekStyle |
pub static DEFAULT_BUF_SIZE: uint = 1024 * 64 |
The default buffer size for various I/O operations XXX: Not pub |
Acceptor | An acceptor is a value that presents incoming connections |
Decorator | Common trait for decorator types. |
Listener | A listener is a value that can consume itself to start listening for connections. Doing so produces some sort of Acceptor. |
Reader | |
Seek | XXX
|
Stream | |
Writer |
ignore_io_error | Helper for wrapper calls where you want to ignore any io_errors that might be raised |
placeholder_error | |
standard_error |
Prefix searches with a type followed by a colon (e.g.
fn:
) to restrict the search to a given type.
Accepted types are: fn
, mod
,
struct
(or str
), enum
,
trait
, typedef
(or
tdef
).