8
8
9
9
Close a hole in encapsulation boundaries in Rust by providing users of
10
10
` AsRawFd ` and related traits guarantees about their raw resource handles, by
11
- introducing a concept of * I/O safety* and a new ` IoSafe ` trait. Build on, and
12
- provide an explanation for, the ` from_raw_fd ` function being unsafe.
11
+ introducing a concept of * I/O safety* and a new set of types and traits.
13
12
14
13
# Motivation
15
14
[ motivation ] : #motivation
@@ -50,7 +49,7 @@ This RFC introduces a path to gradually closing this loophole by introducing:
50
49
51
50
- A new concept, I/O safety, to be documented in the standard library
52
51
documentation.
53
- - A new trait, ` std::io::IoSafe ` .
52
+ - A new set of types and traits .
54
53
- New documentation for
55
54
[ ` from_raw_fd ` ] /[ ` from_raw_handle ` ] /[ ` from_raw_socket ` ] explaining why
56
55
they're unsafe in terms of I/O safety, addressing a question that has
@@ -129,106 +128,71 @@ lifetime the OS associates with the handle.
129
128
130
129
I/O safety is new as an explicit concept, but it reflects common practices.
131
130
Rust's ` std ` will require no changes to stable interfaces, beyond the
132
- introduction of a new trait and new impls for it. Initially, not all of the
133
- Rust ecosystem will support I/O safety though; adoption will be gradual.
131
+ introduction of some new types and traits and new impls for them. Initially,
132
+ not all of the Rust ecosystem will support I/O safety though; adoption will
133
+ be gradual.
134
134
135
- ## The ` IoSafe ` trait
135
+ ## ` OwnedFd ` and ` BorrowedFd<'owned> `
136
136
137
- These high-level types also implement the traits [ ` AsRawFd ` ] /[ ` IntoRawFd ` ] on
138
- Unix-like platforms and
139
- [ ` AsRawHandle ` ] /[ ` AsRawSocket ` ] /[ ` IntoRawHandle ` ] /[ ` IntoRawSocket ` ] on Windows,
140
- providing ways to obtain the low-level value contained in a high-level value.
141
- APIs use these to accept any type containing a raw handle, such as in the
142
- ` do_some_io ` example in the [ motivation] .
137
+ These two types are conceptual replacements for ` RawFd ` , and represent owned
138
+ and borrowed handle values. ` OwnedFd ` owns a file descriptor, including closing
139
+ it when it's dropped. ` BorrowedFd ` 's lifetime parameter ties it to the lifetime
140
+ of something that owns a file descriptor. These types enforce all of their I/O
141
+ safety invariants automatically.
143
142
144
- ` AsRaw* ` and ` IntoRaw* ` don't make any guarantees, so to add I/O safety, types
145
- will implement a new trait, ` IoSafe ` :
143
+ For Windows, similar types, but in ` Handle ` and ` Socket ` forms.
146
144
147
- ``` rust
148
- pub unsafe trait IoSafe {}
149
- ```
150
-
151
- There are no required functions, so implementing it just takes one line, plus
152
- comments:
145
+ ## ` AsFd ` , ` IntoFd ` , and ` FromFd `
153
146
154
- ``` rust
155
- /// # Safety
156
- ///
157
- /// `MyType` wraps a `std::fs::File` which handles the low-level details, and
158
- /// doesn't have a way to reassign or independently drop it.
159
- unsafe impl IoSafe for MyType {}
160
- ```
147
+ These three traits are conceptual replacements for ` AsRawFd ` , ` IntoRawFd ` , and
148
+ ` FromRawFd ` for most use cases. They work in terms of ` OwnedFd ` and
149
+ ` BorrowedFd ` , so they automatically enforce their I/O safety invariants.
161
150
162
- It requires ` unsafe ` , to require the code to explicitly commit to upholding I/O
163
- safety. With ` IoSafe ` , the ` do_some_io ` example should simply add a
164
- ` + IoSafe ` to provide I/O safety:
151
+ Using these traits, the ` do_some_io ` example in the [ motivation] can avoid
152
+ the original problems. Since ` AsFd ` is only implemented for types which
153
+ properly own their file descriptors, this version of ` do_some_io ` doesn't
154
+ have to worry about being passed bogus or dangling file descriptors:
165
155
166
156
``` rust
167
- pub fn do_some_io <FD : AsRawFd + IoSafe >(input : & FD ) -> io :: Result <()> {
168
- some_syscall (input . as_raw_fd ())
157
+ pub fn do_some_io <FD : AsFd >(input : & FD ) -> io :: Result <()> {
158
+ some_syscall (input . as_fd ())
169
159
}
170
160
```
171
161
172
- Some types have the ability to dynamically drop their resources, and
173
- these types require special consideration when implementing ` IoSafe ` . For
174
- example, a class representing a dynamically reassignable output source might
175
- have code like this:
176
-
177
- ``` rust
178
- struct VirtualStdout {
179
- current : RefCell <std :: fs :: File >
180
- }
181
-
182
- impl VirtualStdout {
183
- /// Assign a new output destination.
184
- ///
185
- /// This function ends the lifetime of the resource that `as_raw_fd`
186
- /// returns a handle to.
187
- pub fn set_output (& self , new : std :: fs :: File ) {
188
- * self . current. borrow_mut () = new ;
189
- }
190
- }
162
+ For Windows, similar traits, but in ` Handle ` and ` Socket ` forms.
191
163
192
- impl AsRawFd for VirtualStdout {
193
- fn as_raw_fd (& self ) -> RawFd {
194
- self . current. borrow (). as_raw_fd ()
195
- }
196
- }
197
- ```
164
+ ## Portability for simple use cases
198
165
199
- If a user of this type were to hold a ` RawFd ` value over a call to ` set_file ` ,
200
- the ` RawFd ` value would become dangling, even though its within the lifetime of
201
- the ` &self ` reference passed to ` as_raw_fd ` :
166
+ Portability in this space isn't easy, since Windows has two different handle
167
+ types while Unix has one. However, some use cases can treat ` AsFd ` and
168
+ ` AsHandle ` similarly, while some other uses can treat ` AsFd ` and ` AsSocket `
169
+ similarly. In these two cases, trivial ` Filelike ` and ` Socketlike ` abstractions
170
+ allow code which works in this way to be generic over Unix and Windows.
202
171
203
- ``` rust
204
- fn foo (output : & VirtualStdout ) -> io :: Result <()> {
205
- let raw_fd = output . as_raw_fd ();
206
- output . set_file (File :: open (" /some/other/file" )? );
207
- use (raw_fd )? ; // Use of dangling file descriptor!
208
- Ok (())
209
- }
210
- ```
172
+ On Unix, ` AsFilelike ` and ` AsSocketlike ` have blanket implementations for
173
+ any type that implements ` AsFd ` . On Windows, ` AsFilelike ` has a blanket
174
+ implementation for any type that implements ` AsHandle ` , and ` AsSocketlike `
175
+ has a blanket implementation for any type that implements ` AsSocket ` .
211
176
212
- The ` IoSafe ` trait requires types capable of dynamically dropping their
213
- resources within the lifetime of the ` &self ` passed to ` as_raw_fd ` must
214
- document the conditions under which this can occur, as the documentation
215
- comment above does.
177
+ Similar portability abstractions apply to the ` From* ` and ` Into* ` traits.
216
178
217
179
## Gradual adoption
218
180
219
- I/O safety and ` IoSafe ` wouldn't need to be adopted immediately, adoption
220
- could be gradual:
181
+ I/O safety and the new types and traits wouldn't need to be adopted
182
+ immediately; adoption could be gradual:
221
183
222
- - First, ` std ` adds ` IoSafe ` with impls for all the relevant ` std ` types.
223
- This is a backwards-compatible change.
184
+ - First, ` std ` adds the new types and traits with impls for all the relevant
185
+ ` std ` types. This is a backwards-compatible change.
224
186
225
- - After that, crates could implement ` IoSafe ` for their own types. These
226
- changes would be small and semver-compatible, without special coordination.
187
+ - After that, crates could begin to use the new types and implement the new
188
+ traits for their own types. These changes would be small and semver-compatible,
189
+ without special coordination.
227
190
228
- - Once the standard library and enough popular crates utilize ` IoSafe ` ,
229
- crates could start to add ` + IoSafe ` bounds (or adding ` unsafe ` ), at their
230
- own pace. These would be semver-incompatible changes, though most users of
231
- APIs adding ` + IoSafe ` wouldn't need any changes.
191
+ - Once the standard library and enough popular crates implement the new
192
+ traits, crates could start to switch to using the new traits as bounds when
193
+ accepting generic arguments, at their own pace. These would be
194
+ semver-incompatible changes, though most users of APIs switching to these
195
+ new traits wouldn't need any changes.
232
196
233
197
# Reference-level explanation
234
198
[ reference-level-explanation ] : #reference-level-explanation
@@ -250,44 +214,52 @@ Functions accepting arbitrary raw I/O handle values ([`RawFd`], [`RawHandle`],
250
214
or [ ` RawSocket ` ] ) should be ` unsafe ` if they can lead to any I/O being
251
215
performed on those handles through safe APIs.
252
216
253
- Functions accepting types implementing
254
- [ ` AsRawFd ` ] /[ ` IntoRawFd ` ] /[ ` AsRawHandle ` ] /[ ` AsRawSocket ` ] /[ ` IntoRawHandle ` ] /[ ` IntoRawSocket ` ]
255
- should add a ` + IoSafe ` bound if they do I/O with the returned raw handle.
217
+ ## ` OwnedFd ` and ` BorrowedFd<'owned> `
218
+
219
+ ` OwnedFd ` and ` BorrowedFd ` are both ` repr(transparent) ` with a ` RawFd ` value
220
+ on the inside, and both can use niche optimizations so that ` Option<OwnedFd> `
221
+ and ` Option<BorrowedFd<'_>> ` are the same size, and can be used in FFI
222
+ declarations for functions like ` open ` , ` read ` , ` write ` , ` close ` , and so on.
223
+ When used this way, they ensure I/O safety all the way out to the FFI boundary.
224
+
225
+ These types also implement the existing ` AsRawFd ` , ` IntoRawFd ` , and ` FromRawFd `
226
+ traits, so they can interoperate with existing code that works with ` RawFd `
227
+ types.
228
+
229
+ ## ` AsFd ` , ` IntoFd ` , and ` FromFd `
256
230
257
- ## The ` IoSafe ` trait
231
+ These types provide ` as_fd ` , ` into_fd ` , and ` from_fd ` functions similar to
232
+ their ` Raw ` counterparts, but with the benefit of a safe interface, it's safe
233
+ to provide a few simple conveniences which make the API much more flexible:
258
234
259
- Types implementing ` IoSafe ` guarantee that they uphold I/O safety. They must
260
- not make it possible to write a safe function which can perform invalid I/O
261
- operations , and:
235
+ - A ` from_into_fd ` function which takes a ` IntoFd ` and converts it into a
236
+ ` FromFd ` , allowing users to perform this common sequence in a single
237
+ step , and without having to use ` unsafe ` .
262
238
263
- - A type implementing ` AsRaw* + IoSafe ` means its ` as_raw_* ` function returns
264
- a handle which is valid to use for the duration of the ` &self ` reference.
265
- If such types have methods to close or reassign the handle without
266
- dropping the whole object, they must document the conditions under which
267
- existing raw handle values remain valid to use.
239
+ - A ` as_filelike_view::<T>() ` function returns a ` View ` , which contains a
240
+ temporary ` ManuallyDrop ` instance of T constructed from the contained
241
+ file descriptor, allowing users to "view" a raw file descriptor as a
242
+ ` File ` , ` TcpStream ` , and so on.
268
243
269
- - A type implementing ` IntoRaw* + IoSafe ` means its ` into_raw_* ` function
270
- returns a handle which is valid to use at the point of the return from
271
- the call.
244
+ ## Prototype implementation
272
245
273
- All standard library types implementing ` AsRawFd ` implement ` IoSafe ` , except
274
- ` RawFd ` .
246
+ All of the above is prototyped here:
275
247
276
- Note that, despite the naming similarity, the ` IoSafe ` trait's requirements are not
277
- identical to the I/O safety requirements. The return value of ` as_raw_* ` is
278
- valid only for the duration of the ` &self ` argument passed in.
248
+ < https://github.com/sunfishcode/io-experiment >
249
+
250
+ The README.md has links to documentation, examples, and a survey of existing
251
+ crates providing similar features.
279
252
280
253
# Drawbacks
281
254
[ drawbacks ] : #drawbacks
282
255
283
256
Crates with APIs that use file descriptors, such as [ ` nix ` ] and [ ` mio ` ] , would
284
- need to migrate to types implementing ` AsRawFd + IoSafe ` , use crates providing
285
- equivalent mechanisms such as [ ` unsafe-io ` ] , or change such functions to be
257
+ need to migrate to types implementing ` AsFd ` , or change such functions to be
286
258
unsafe.
287
259
288
260
Crates using ` AsRawFd ` or ` IntoRawFd ` to accept "any file-like type" or "any
289
261
socket-like type", such as [ ` socket2 ` ] 's [ ` SockRef::from ` ] , would need to
290
- either add a ` + IoSafe ` bound or make these functions unsafe.
262
+ either switch to ` AsFd ` or ` IntoFd ` , or make these functions unsafe.
291
263
292
264
# Rationale and alternatives
293
265
[ rationale-and-alternatives ] : #rationale-and-alternatives
@@ -347,52 +319,19 @@ I/O safety approach will require changes to Rust code in crates such as
347
319
[ ` RawFd ` ] , though the changes can be made gradually across the ecosystem rather
348
320
than all at once.
349
321
350
- ## I/O safety but not ` IoSafe `
351
-
352
- The I/O safety concept doesn't depend on ` IoSafe ` being in ` std ` . Crates could
353
- continue to use [ ` unsafe_io::OwnsRaw ` ] , though that does involve adding a
354
- dependency.
355
-
356
- ## Define ` IoSafe ` in terms of the object, not the reference
357
-
358
- The [ reference-level-explanation] explains ` IoSafe + AsRawFd ` as returning a
359
- handle valid to use for "the duration of the ` &self ` reference". This makes it
360
- similar to borrowing a reference to the handle, though it still uses a raw
361
- type which doesn't enforce the borrowing rules.
362
-
363
- An alternative would be to define it in terms of the underlying object. Since
364
- it returns raw types, arguably it would be better to make it work more like
365
- ` slice::as_ptr ` and other functions which return raw pointers that aren't
366
- connected to reference lifetimes. If the concept of borrowing is desired, new
367
- types could be introduced, with better ergonomics, in a separate proposal.
368
-
369
- ## New types and traits
370
-
371
- New types and traits could provide a much cleaner API, along the lines of:
372
-
373
- ``` rust
374
- pub struct BorrowedFd <'owned > { ... }
375
- pub struct OwnedFd { ... }
376
-
377
- pub trait AsFd { ... }
378
- pub trait IntoFd { ... }
379
- pub trait FromFd { ... }
380
- ```
322
+ ## The ` IoSafe ` trait (and ` OwnsRaw ` before it)
381
323
382
- An initial prototype of this here:
383
-
384
- < https://github.com/sunfishcode/io-experiment >
324
+ Earlier versions of this RFC proposed an ` IoSafe ` trait, which was meant as a
325
+ minimally intrusive fix. Feedback from the RFC process led to the development
326
+ of a new set of types and traits. This has a much larger API surface area,
327
+ which will take more work to design and review. And it and will require more
328
+ extensive changes in the crates ecosystem over time. However, early indications
329
+ are that the new types and traits are easier to understand, and easier and
330
+ safer to use, and so are a better foundation for the long term.
385
331
386
- The details are mostly obvious, though one notable aspect of this design is
387
- the use of ` repr(transparent) ` to define types that can participate in FFI
388
- directly, leading to FFI usage patterns that don't interact with raw types
389
- at all. An example of this is here:
390
-
391
- < https://github.com/sunfishcode/io-experiment/blob/main/examples/hello.rs >
392
-
393
- This provides a cleaner API than ` *Raw* ` + ` IoSafe ` . The main obvious downside
394
- is that a lot of code will likely need to continue to support ` *Raw* ` for a
395
- long time, so this would increase the amount of code they have to maintain.
332
+ Earlier versions of ` IoSafe ` were called ` OwnsRaw ` . It was difficult to find a
333
+ name for this trait which described exactly what it does, and arguably this is
334
+ one of the signs that it wasn't the right trait.
396
335
397
336
# Prior art
398
337
[ prior-art ] : #prior-art
@@ -403,9 +342,15 @@ such as in [C#], [Java], and others. Making it `unsafe` to perform I/O through
403
342
a given raw handle would let safe Rust have the same guarantees as those
404
343
effectively provided by such languages.
405
344
406
- The ` std::io::IoSafe ` trait comes from [ ` unsafe_io::OwnsRaw ` ] , and experience
407
- with this trait, including in some production use cases, has shaped this RFC.
345
+ There are several crates on crates.io providing owning and borrowing file
346
+ descriptor wrappers. The [ io-experiment README.md's Prior Art section]
347
+ describes these and details how io-experiment's similarities and differences
348
+ with these existing crates in detail. At a high level, these existing crates
349
+ share the same basic concepts that io-experiment uses. All are built around
350
+ Rust's lifetime and ownership concepts, and confirm that these concepts
351
+ are a good fit for this problem.
408
352
353
+ [ io-experiment README.md's Prior Art section ] : https://github.com/sunfishcode/io-experiment#prior-art
409
354
[ C# ] : https://docs.microsoft.com/en-us/dotnet/api/system.io.file?view=net-5.0
410
355
[ Java ] : https://docs.oracle.com/javase/7/docs/api/java/io/File.html?is-external=true
411
356
@@ -432,17 +377,6 @@ needs, but it could be explored in the future.
432
377
433
378
Some possible future ideas that could build on this RFC include:
434
379
435
- - New wrapper types around ` RawFd ` /` RawHandle ` /` RawSocket ` , to improve the
436
- ergonomics of some common use cases. Such types may also provide portability
437
- features as well, abstracting over some of the ` Fd ` /` Handle ` /` Socket `
438
- differences between platforms.
439
-
440
- - Higher-level abstractions built on ` IoSafe ` . Features like
441
- [ ` from_filelike ` ] and others in [ ` unsafe-io ` ] eliminate the need for
442
- ` unsafe ` in user code in some common use cases. [ ` posish ` ] uses this to
443
- provide safe interfaces for POSIX-like functionality without having ` unsafe `
444
- in user code, such as in [ this wrapper around ` posix_fadvise ` ] .
445
-
446
380
- Clippy lints warning about common I/O-unsafe patterns.
447
381
448
382
- A formal model of ownership for raw handles. One could even imagine
0 commit comments