Skip to content

Commit c0e734d

Browse files
committed
core::rt: Add more I/O docs
1 parent 6373861 commit c0e734d

File tree

2 files changed

+153
-22
lines changed

2 files changed

+153
-22
lines changed

src/libcore/rt/io/mod.rs

Lines changed: 151 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010

1111
/*! Synchronous I/O
1212
13-
This module defines the Rust interface for synchronous I/O. It is
14-
build around Reader and Writer traits that define byte stream sources
15-
and sinks. Implementations are provided for common I/O streams like
16-
file, TCP, UDP, Unix domain sockets, multiple types of memory bufers.
17-
Readers and Writers may be composed to add things like string parsing,
18-
and compression.
13+
This module defines the Rust interface for synchronous I/O.
14+
It models byte-oriented input and output with the Reader and Writer traits.
15+
Types that implement both `Reader` and `Writer` and called 'streams',
16+
and automatically implement trait `Stream`.
17+
Implementations are provided for common I/O streams like
18+
file, TCP, UDP, Unix domain sockets.
19+
Readers and Writers may be composed to add capabilities like string
20+
parsing, encoding, and compression.
1921
2022
This will likely live in core::io, not core::rt::io.
2123
@@ -31,22 +33,22 @@ Some examples of obvious things you might want to do
3133
3234
* Read a complete file to a string, (converting newlines?)
3335
34-
let contents = FileStream::open("message.txt").read_to_str(); // read_to_str??
36+
let contents = File::open("message.txt").read_to_str(); // read_to_str??
3537
3638
* Write a line to a file
3739
38-
let file = FileStream::open("message.txt", Create, Write);
40+
let file = File::open("message.txt", Create, Write);
3941
file.write_line("hello, file!");
4042
4143
* Iterate over the lines of a file
4244
43-
do FileStream::open("message.txt").each_line |line| {
45+
do File::open("message.txt").each_line |line| {
4446
println(line)
4547
}
4648
4749
* Pull the lines of a file into a vector of strings
4850
49-
let lines = FileStream::open("message.txt").line_iter().to_vec();
51+
let lines = File::open("message.txt").line_iter().to_vec();
5052
5153
* Make an simple HTTP request
5254
@@ -63,25 +65,145 @@ Some examples of obvious things you might want to do
6365
6466
# Terms
6567
66-
* reader
67-
* writer
68-
* stream
69-
* Blocking vs. non-blocking
70-
* synchrony and asynchrony
71-
72-
I tend to call this implementation non-blocking, because performing I/O
73-
doesn't block the progress of other tasks. Is that how we want to present
74-
it, 'synchronous but non-blocking'?
68+
* Reader - An I/O source, reads bytes into a buffer
69+
* Writer - An I/O sink, writes bytes from a buffer
70+
* Stream - Typical I/O sources like files and sockets are both Readers and Writers,
71+
and are collectively referred to a `streams`.
72+
* Decorator - A Reader or Writer that composes with others to add additional capabilities
73+
such as encoding or decoding
74+
75+
# Blocking and synchrony
76+
77+
When discussing I/O you often hear the terms 'synchronous' and
78+
'asynchronous', along with 'blocking' and 'non-blocking' compared and
79+
contrasted. A synchronous I/O interface performs each I/O operation to
80+
completion before proceeding to the next. Synchronous interfaces are
81+
usually used in imperative style as a sequence of commands. An
82+
asynchronous interface allows multiple I/O requests to be issued
83+
simultaneously, without waiting for each to complete before proceeding
84+
to the next.
85+
86+
Asynchronous interfaces are used to achieve 'non-blocking' I/O. In
87+
traditional single-threaded systems, performing a synchronous I/O
88+
operation means that the program stops all activity (it 'blocks')
89+
until the I/O is complete. Blocking is bad for performance when
90+
there are other computations that could be done.
91+
92+
Asynchronous interfaces are most often associated with the callback
93+
(continuation-passing) style popularised by node.js. Such systems rely
94+
on all computations being run inside an event loop which maintains a
95+
list of all pending I/O events; when one completes the registered
96+
callback is run and the code that made the I/O request continiues.
97+
Such interfaces achieve non-blocking at the expense of being more
98+
difficult to reason about.
99+
100+
Rust's I/O interface is synchronous - easy to read - and non-blocking by default.
101+
102+
Remember that Rust tasks are 'green threads', lightweight threads that
103+
are multiplexed onto a single operating system thread. If that system
104+
thread blocks then no other task may proceed. Rust tasks are
105+
relatively cheap to create, so as long as other tasks are free to
106+
execute then non-blocking code may be written by simply creating a new
107+
task.
108+
109+
When discussing blocking in regards to Rust's I/O model, we are
110+
concerned with whether performing I/O blocks other Rust tasks from
111+
proceeding. In other words, when a task calls `read`, it must then
112+
wait (or 'sleep', or 'block') until the call to `read` is complete.
113+
During this time, other tasks may or may not be executed, depending on
114+
how `read` is implemented.
115+
116+
117+
Rust's default I/O implementation is non-blocking; by cooperating
118+
directly with the task scheduler it arranges to never block progress
119+
of *other* tasks. Under the hood, Rust uses asynchronous I/O via a
120+
per-scheduler (and hence per-thread) event loop. Synchronous I/O
121+
requests are implemented by descheduling the running task and
122+
performing an asynchronous request; the task is only resumed once the
123+
asynchronous request completes.
124+
125+
For blocking (but possibly more efficient) implementations, look
126+
in the `io::native` module.
75127
76128
# Error Handling
77129
130+
I/O is an area where nearly every operation can result in unexpected
131+
errors. It should allow errors to be handled efficiently.
132+
It needs to be convenient to use I/O when you don't care
133+
about dealing with specific errors.
134+
135+
Rust's I/O employs a combination of techniques to reduce boilerplate
136+
while still providing feedback about errors. The basic strategy:
137+
138+
* Errors are fatal by default, resulting in task failure
139+
* Errors raise the `io_error` conditon which provides an opportunity to inspect
140+
an IoError object containing details.
141+
* Return values must have a sensible null or zero value which is returned
142+
if a condition is handled successfully. This may be an `Option`, an empty
143+
vector, or other designated error value.
144+
* Common traits are implemented for `Option`, e.g. `impl<R: Reader> Reader for Option<R>`,
145+
so that nullable values do not have to be 'unwrapped' before use.
146+
147+
These features combine in the API to allow for expressions like
148+
`File::new("diary.txt").write_line("met a girl")` without having to
149+
worry about whether "diary.txt" exists or whether the write
150+
succeeds. As written, if either `new` or `write_line` encounters
151+
an error the task will fail.
152+
153+
If you wanted to handle the error though you might write
154+
155+
let mut error = None;
156+
do io_error::cond(|e: IoError| {
157+
error = Some(e);
158+
}).in {
159+
File::new("diary.txt").write_line("met a girl");
160+
}
161+
162+
if error.is_some() {
163+
println("failed to write my diary");
164+
}
165+
166+
XXX: Need better condition handling syntax
167+
168+
In this case the condition handler will have the opportunity to
169+
inspect the IoError raised by either the call to `new` or the call to
170+
`write_line`, but then execution will continue.
171+
172+
So what actually happens if `new` encounters an error? To understand
173+
that it's important to know that what `new` returns is not a `File`
174+
but an `Option<File>`. If the file does not open, and the condition
175+
is handled, then `new` will simply return `None`. Because there is an
176+
implementation of `Writer` (the trait required ultimately required for
177+
types to implement `write_line`) there is no need to inspect or unwrap
178+
the `Option<File>` and we simply call `write_line` on it. If `new`
179+
returned a `None` then the followup call to `write_line` will also
180+
raise an error.
181+
182+
## Concerns about this strategy
183+
184+
This structure will encourage a programming style that is prone
185+
to errors similar to null pointer dereferences.
186+
In particular code written to ignore errors and expect conditions to be unhandled
187+
will start passing around null or zero objects when wrapped in a condition handler.
188+
189+
* XXX: How should we use condition handlers that return values?
190+
191+
192+
# Issues withi/o scheduler affinity, work stealing, task pinning
193+
78194
# Resource management
79195
80196
* `close` vs. RAII
81197
82-
# Paths and URLs
198+
# Paths, URLs and overloaded constructors
199+
200+
83201
84-
# std
202+
# Scope
203+
204+
In scope for core
205+
206+
* Url?
85207
86208
Some I/O things don't belong in core
87209
@@ -90,7 +212,12 @@ Some I/O things don't belong in core
90212
- http
91213
- flate
92214
93-
# XXX
215+
Out of scope
216+
217+
* Async I/O. We'll probably want it eventually
218+
219+
220+
# XXX Questions and issues
94221
95222
* Should default constructors take `Path` or `&str`? `Path` makes simple cases verbose.
96223
Overloading would be nice.
@@ -100,6 +227,7 @@ Some I/O things don't belong in core
100227
* fsync
101228
* relationship with filesystem querying, Directory, File types etc.
102229
* Rename Reader/Writer to ByteReader/Writer, make Reader/Writer generic?
230+
* Can Port and Chan be implementations of a generic Reader<T>/Writer<T>?
103231
* Trait for things that are both readers and writers, Stream?
104232
* How to handle newline conversion
105233
* String conversion
@@ -109,6 +237,7 @@ Some I/O things don't belong in core
109237
* Do we need `close` at all? dtors might be good enough
110238
* How does I/O relate to the Iterator trait?
111239
* std::base64 filters
240+
* Using conditions is a big unknown since we don't have much experience with them
112241
113242
*/
114243

src/libcore/rt/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
/*! The Rust runtime, including the scheduler and I/O interface */
12+
1113
#[doc(hidden)];
1214

1315
use libc::c_char;

0 commit comments

Comments
 (0)