10
10
11
11
/*! Synchronous I/O
12
12
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.
19
21
20
22
This will likely live in core::io, not core::rt::io.
21
23
@@ -31,22 +33,22 @@ Some examples of obvious things you might want to do
31
33
32
34
* Read a complete file to a string, (converting newlines?)
33
35
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??
35
37
36
38
* Write a line to a file
37
39
38
- let file = FileStream ::open("message.txt", Create, Write);
40
+ let file = File ::open("message.txt", Create, Write);
39
41
file.write_line("hello, file!");
40
42
41
43
* Iterate over the lines of a file
42
44
43
- do FileStream ::open("message.txt").each_line |line| {
45
+ do File ::open("message.txt").each_line |line| {
44
46
println(line)
45
47
}
46
48
47
49
* Pull the lines of a file into a vector of strings
48
50
49
- let lines = FileStream ::open("message.txt").line_iter().to_vec();
51
+ let lines = File ::open("message.txt").line_iter().to_vec();
50
52
51
53
* Make an simple HTTP request
52
54
@@ -63,25 +65,145 @@ Some examples of obvious things you might want to do
63
65
64
66
# Terms
65
67
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.
75
127
76
128
# Error Handling
77
129
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
+
78
194
# Resource management
79
195
80
196
* `close` vs. RAII
81
197
82
- # Paths and URLs
198
+ # Paths, URLs and overloaded constructors
199
+
200
+
83
201
84
- # std
202
+ # Scope
203
+
204
+ In scope for core
205
+
206
+ * Url?
85
207
86
208
Some I/O things don't belong in core
87
209
@@ -90,7 +212,12 @@ Some I/O things don't belong in core
90
212
- http
91
213
- flate
92
214
93
- # XXX
215
+ Out of scope
216
+
217
+ * Async I/O. We'll probably want it eventually
218
+
219
+
220
+ # XXX Questions and issues
94
221
95
222
* Should default constructors take `Path` or `&str`? `Path` makes simple cases verbose.
96
223
Overloading would be nice.
@@ -100,6 +227,7 @@ Some I/O things don't belong in core
100
227
* fsync
101
228
* relationship with filesystem querying, Directory, File types etc.
102
229
* Rename Reader/Writer to ByteReader/Writer, make Reader/Writer generic?
230
+ * Can Port and Chan be implementations of a generic Reader<T>/Writer<T>?
103
231
* Trait for things that are both readers and writers, Stream?
104
232
* How to handle newline conversion
105
233
* String conversion
@@ -109,6 +237,7 @@ Some I/O things don't belong in core
109
237
* Do we need `close` at all? dtors might be good enough
110
238
* How does I/O relate to the Iterator trait?
111
239
* std::base64 filters
240
+ * Using conditions is a big unknown since we don't have much experience with them
112
241
113
242
*/
114
243
0 commit comments