This library introduces OwnedFd
, BorrowedFd
, and supporting types and
traits, and corresponding features for Windows, which implement safe owning
and borrowing I/O lifetime patterns.
This is associated with RFC 3128, the I/O Safety RFC, which proposes that
this API be added to std
, with the goal being to eventually replace RawFd
etc. for most use cases.
Some features currently require nightly Rust, as they depend on rustc_attrs
to perform niche optimizations needed for FFI use cases.
For a quick taste, check out the code examples:
- hello, a basic demo of this API, doing low-level I/O manually, using the provided example FFI bindings
- easy-conversions, demonstrating the
from_into
convenience feature for converting from animpl Into*
into animpl From*
. - portable-views, demonstrating the convenience feature which allows one
to temporarily "view" a file descriptor as any owning type such as
File
- [flexible-apis], demonstrating how to write library APIs that accept untyped I/O resources.
- owning-wrapper, demonstrating how to implement a type which wraps an
Owned*
type.
The core of the API is very simple, and consists of two main types and three main traits:
On Windows, there are Handle
and Socket
versions of every Fd
thing, and
a special OptionFileHandle
type to cope with inconsistent error reporting
in the Windows API.
Full API documentation:
The magic of transparency
Here's the fun part. BorrowedFd
and OwnedFd
are repr(transparent)
and
hold RawFd
values, and Option<BorrowedFd>
and Option<OwnedFd>
are
FFI-safe (on nightly Rust), so they can all be used in FFI directly:
extern "C"
With bindings like this, users never have to touch RawFd
values. Of course,
not all code will do this, but it is a fun feature for code that can. This
is what motivates having BorrowedFd
instead of just using &OwnedFd
.
Note the use of Option<OwnedFd>
as the return value of open
, representing
the fact that it can either succeed or fail.
Prior Art
There are several similar crates: fd, filedesc, filedescriptor, owned-fd, and unsafe-io.
Some of these provide additional features such as the ability to create pipes or sockets, to get and set flags, and to do read and write operations. io-lifetimes omits these features, leaving them to to be provided as separate layers on top.
Most of these crates provide ways to duplicate a file descriptor. io-lifetimes currently treats this as another feature than can be provided by a layer on top, though if there are use cases where this is a common operation, it could be added.
io-lifetimes's distinguishing features are its use of repr(transparent)
to support direct FFI usage, niche optimizations so Option
can support direct
FFI usafe as well (on nightly Rust), lifetime-aware As*
/Into*
/From*
traits which leverage Rust's lifetime system and allow safe and checked
from_*
and as_*
/into_*
functions, and powerful convenience features
enabled by its underlying safety.
io-lifetimes also has full Windows support, as well as Unix/Windows portability abstractions, covering both file-like and socket-like types.
io-lifetimes's OwnedFd
type is similar to
fd's
FileDesc
. io-lifetimes
doesn't have a close_on_drop
parameter, and instead uses OwnedFd
and
BorrowedFd
to represent dropping and non-dropping handles, respectively, in
a way that is checked at compile time rather than runtime.
io-lifetimes's OwnedFd
type is also similar to
filedesc's
FileDesc
io-lifetimes's OwnedFd
reserves the value -1, so it doesn't need to test for
-1
in its Drop
, and Option<OwnedFd>
is the same size as OwnedFd
(on
nightly Rust).
io-lifetimes's OwnedFd
type is also similar to
owned-fd's
OwnedFd
.
io-lifetimes doesn't implement Clone
, because duplicating a file descriptor
can fail due to OS process limits, while Clone
is an infallible interface.
io-lifetimes's BorrowedFd
is similar to
owned-fd's
FdRef
, except it
uses a lifetime parameter and PhantomData
rather than transmuting a raw file
descriptor value into a reference value.
io-lifetimes's convenience features are similar to those of
unsafe-io, but io-lifetimes is built on
its own As*
/Into*
/From*
traits, rather than extending
AsRaw*
/IntoRaw*
/FromRaw*
with
OwnsRaw
, so
they're simpler and safer to use. io-lifetimes doesn't include unsafe-io's
*ReadWrite*
or *HandleOrSocket*
abstractions, and leaves these as features
to be provided by separate layers on top.