Copy path View file
@@ -0,0 +1,550 @@
- Start Date: 2014-12-07
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary
[Summary]: #summary

This RFC proposes a significant redesign of the `std::io` and `std::os` modules
in preparation for API stabilization. The specific problems addressed by the
redesign are given in the [Problems] section below, and the key ideas of the
design are given in [Vision for IO].

# Note about RFC structure

This RFC was originally posted as a single monolithic file, which made
it difficult to discuss different parts separately.

It has now been split into a skeleton that covers (1) the problem
statement, (2) the overall vision and organization, and (3) the
`std::os` module.

Other parts of the RFC are marked with `(stub)` and will be filed as
follow-up PRs against this RFC.

# Table of contents
[Table of contents]: #table-of-contents
* [Summary]
* [Table of contents]
* [Problems]
* [Atomicity and the `Reader`/`Writer` traits]
* [Timeouts]
* [Posix and libuv bias]
* [Unicode]
* [stdio]
* [Overly high-level abstractions]
* [The error chaining pattern]
* [Detailed design]
* [Vision for IO]
* [Goals]
* [Design principles]
* [What cross-platform means]
* [Relation to the system-level APIs]
* [Platform-specific opt-in]
* [Proposed organization]
* [Revising `Reader` and `Writer`] (stub)
* [String handling] (stub)
* [Deadlines] (stub)
* [Splitting streams and cancellation] (stub)
* [Modules]
* [core::io] (stub)
* [The std::io facade] (stub)
* [std::env] (stub)
* [std::fs] (stub)
* [std::net] (stub)
* [std::process] (stub)
* [std::os]
* [Odds and ends]
* [The io prelude]
* [Drawbacks]
* [Alternatives]
* [Unresolved questions]

# Problems
[Problems]: #problems

The `io` and `os` modules are the last large API surfaces of `std` that need to
be stabilized. While the basic functionality offered in these modules is
*largely* traditional, many problems with the APIs have emerged over time. The
RFC discusses the most significant problems below.

This section only covers specific problems with the current library; see
[Vision for IO] for a higher-level view. section.

This comment has been minimized.

@untitaker

untitaker Jan 23, 2015

Contributor

...for a higher-level view. section.

Typo?


## Atomicity and the `Reader`/`Writer` traits
[Atomicity and the `Reader`/`Writer` traits]: #atomicity-and-the-readerwriter-traits

One of the most pressing -- but also most subtle -- problems with `std::io` is
the lack of *atomicity* in its `Reader` and `Writer` traits.

For example, the `Reader` trait offers a `read_to_end` method:

```rust
fn read_to_end(&mut self) -> IoResult<Vec<u8>>
```
Executing this method may involve many calls to the underlying `read`
method. And it is possible that the first several calls succeed, and then a call
returns an `Err` -- which, like `TimedOut`, could represent a transient
problem. Unfortunately, given the above signature, there is no choice but to
simply _throw this data away_.
The `Writer` trait suffers from a more fundamental problem, since its primary
method, `write`, may actually involve several calls to the underlying system --
and if a failure occurs, there is no indication of how much was written.
Existing blocking APIs all have to deal with this problem, and Rust can and
should follow the existing tradition here. See [Revising `Reader` and `Writer`] for the proposed
solution.
## Timeouts
[Timeouts]: #timeouts
The `std::io` module supports "timeouts" on virtually all IO objects via a
`set_timeout` method. In this design, every IO object (file, socket, etc.) has
an optional timeout associated with it, and `set_timeout` mutates the associated
timeout. All subsequent blocking operations are implicitly subject to this timeout.
This API choice suffers from two problems, one cosmetic and the other deeper:
* The "timeout" is
[actually a *deadline*](https://github.com/rust-lang/rust/issues/15802) and
should be named accordingly.
* The stateful API has poor composability: when passing a mutable reference of
an IO object to another function, it's possible that the deadline has been
changed. In other words, users of the API can easily interfere with each other
by accident.
See [Deadlines] for the proposed solution.
## Posix and libuv bias
[Posix and libuv bias]: #posix-and-libuv-bias
The current `io` and `os` modules were originally designed when `librustuv` was
providing IO support, and to some extent they reflect the capabilities and
conventions of `libuv` -- which in turn are loosely based on Posix.

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

I don't really think that's true about libuv. If anything, the async model in libuv is closest to IOCP and I don't think the conventions are any more Unix-like than Windows.

This comment has been minimized.

@alexcrichton

alexcrichton Dec 13, 2014

Member

This is specifically referring to other artifacts like naming (posix-biased) as well as cross-platform abstractions like unifying named pipes and unix domain sockets (which have fairly different models and more functionality independently of one another).

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

This is specifically referring to other artifacts like naming (posix-biased)

Windows tends to expose functions using the same naming.

as well as cross-platform abstractions like unifying named pipes and unix domain sockets (which have fairly different models and more functionality independently of one another).

I don't see how that's biased towards POSIX.

As such, the modules are not always ideal from a cross-platform standpoint, both

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

Based on the contents of this RFC, that's not true at all. There are a lot of incorrect assumptions about missing functionality on Windows and no accurate examples of cases where the functionality is missing.

This comment has been minimized.

@alexcrichton

alexcrichton Dec 13, 2014

Member

As noted in this comment, this is not a declaration that windows does not contain the functionality of unix, but rather the current implementation does not handle the windows/unix split gracefully. Abstractions for windows will be developed independently and if a cross-platform interface can be exposed then it will.

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

So why is it moving stuff like fdatasync and pipes that already support both platforms perfectly well?

This comment has been minimized.

@alexcrichton

alexcrichton Dec 13, 2014

Member

The movement of datasync is reflecting that the purpose of the function (flush everything but metadata) is not currently implemented on windows. In this sense we can provide a lossy cross platform abstraction (just use the normal sync on windows), but it's not necessarily what you were asking for. Cases like this are pretty borderline though, and could go one way or the other.

APIs like unix domain sockets/named pipes (currently std::io::net::pipe) are being moved because the underlying models are fairly radically different and we would like to take some time to invest in developing the underlying os-specific abstraction before creating a cross-platform abstraction. The current API of abstracting across the two with a unix-domain-socket-like API is a relic of using libuv (it's what they did), and may not be the API that we'd like to stabilize on. In general it seemed like a port of std::io that was a clear candidate for moving into os::platform and evolving into std::net at a later date.

Functionality like io::pipe has been moved into os::platform because the windows implementation relies on _pipe which is not something we are willing to commit to. Over time we can develop a cross-platform abstraction and then promote these apis back to `std::io.

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

The movement of datasync is reflecting that the purpose of the function (flush everything but metadata) is not currently implemented on windows. In this sense we can provide a lossy cross platform abstraction (just use the normal sync on windows), but it's not necessarily what you were asking for. Cases like this are pretty borderline though, and could go one way or the other.

You've obviously ignored my points in the discussion because you're continuing to spread misinformation. The claim that it is a lossy platform abstraction is completely false. There is nothing lossy about an implementation in terms of fsync. Per the documentation, it may be (and often is) implemented in the same way as fsync or even syncfs. There are many filesystems on *nix where it is the same operation. It is intended to be that way.

APIs like unix domain sockets/named pipes (currently std::io::net::pipe) are being moved because the underlying models are fairly radically different and we would like to take some time to invest in developing the underlying os-specific abstraction before creating a cross-platform abstraction. The current API of abstracting across the two with a unix-domain-socket-like API is a relic of using libuv (it's what they did), and may not be the API that we'd like to stabilize on. In general it seemed like a port of std::io that was a clear candidate for moving into os::platform and evolving into std::net at a later date.

I think you're wrong on that issue too but it's not what I'm talking about here.

Functionality like io::pipe has been moved into os::platform because the windows implementation relies on _pipe which is not something we are willing to commit to. Over time we can develop a cross-platform abstraction and then promote these apis back to `std::io.

The _pipe function calls CreatePipe and if your problem with it is that it's using the CRT it could be switched over. I don't see you asking for liballoc to be removed because it calls malloc instead of HeapAlloc. There are many calls into the CRT in the Rust standard libraries. They can be eliminated without removing functionality when it's just an implementation detail. I don't understand this reasoning at all.

in terms of forcing Windows programmings into a Posix mold, and also of offering
APIs that are not actually usable on all platforms.
The modules have historically also provided *no* platform-specific APIs.
Part of the goal of this RFC is to set out a clear and extensible story for both

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

It's not at all clear to me what the goal is. Windows has the functionality that's being moved to a POSIX module...

This comment has been minimized.

@alexcrichton

alexcrichton Dec 13, 2014

Member

Does this comment help clear it up for you? Movement into os::platform is emphasizing that implementations are lacking today, but have the space to grow into official cross-platform abstractions later.

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

No, it doesn't. Functionality that's already portable is being removed.

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

Even for cases without a Windows implementation (lstat), the fix is generally so simple that I can't see how moving it is justified. This (lack of) reasoning means that there's going to end up being a bunch of legacy platform-specific wrapper APIs with no purpose beyond confusing people and making code less portable.

cross-platform and platform-specific APIs in `std`. See [Design principles] for
the details.
## Unicode
[Unicode]: #unicode
Rust has followed the [utf8 everywhere](http://utf8everywhere.org/) approach to
its strings. However, at the borders to platform APIs, it is revealed that the
world is not, in fact, UTF-8 (or even Unicode) everywhere.
Currently our story for platform APIs is that we either assume they can take or
return Unicode strings (suitably encoded) or an uninterpreted byte
sequence. Sadly, this approach does *not* actually cover all platform needs, and
is also not highly ergonomic as presently implemented. (Consider `os::getev`

This comment has been minimized.

@nagisa

nagisa Jan 13, 2015

Contributor

s/getev/getenv/

which introduces replacement characters (!) versus `os::getenv_as_bytes` which
yields a `Vec<u8>`; neither is ideal.)
This topic was covered in some detail in the
[Path Reform RFC](https://github.com/rust-lang/rfcs/pull/474), but this RFC
gives a more general account in [String handling].
## `stdio`
[stdio]: #stdio
The `stdio` module provides access to readers/writers for `stdin`, `stdout` and
`stderr`, which is essential functionality. However, it *also* provides a means
of changing e.g. "stdout" -- but there is no connection between these two! In
particular, `set_stdout` affects only the writer that `println!` and friends
use, while `set_stderr` affects `panic!`.

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

Panics shouldn't have side effects in the first place. The parent can print to a stream when it handles the error if that's what is desired. If the exception isn't handled, then it should bring down the process. Silencing logic errors is a bad approach, as is dealing with error reporting by hard-wiring lackluster error printing.

This module needs to be clarified. See [The std::io facade] and
[Functionality moved elsewhere] for the detailed design.
## Overly high-level abstractions
[Overly high-level abstractions]: #overly-high-level-abstractions
There are a few places where `io` provides high-level abstractions over system
services without also providing more direct access to the service as-is. For example:
* The `Writer` trait's `write` method -- a cornerstone of IO -- actually
corresponds to an unbounded number of invocations of writes to the underlying
IO object. This RFC changes `write` to follow more standard, lower-level
practice; see [Revising `Reader` and `Writer`].
* Objects like `TcpStream` are `Clone`, which involves a fair amount of
supporting infrastructure. This RFC tackles the problems that `Clone` was
trying to solve more directly; see [Splitting streams and cancellation].
The motivation for going lower-level is described in [Design principles] below.
## The error chaining pattern
[The error chaining pattern]: #the-error-chaining-pattern
The `std::io` module is somewhat unusual in that most of the functionality it
proves are used through a few key traits (like `Reader`) and these traits are in
turn "lifted" over `IoResult`:
```rust
impl<R: Reader> Reader for IoResult<R> { ... }
```

This lifting and others makes it possible to chain IO operations that might
produce errors, without any explicit mention of error handling:

```rust
File::open(some_path).read_to_end()
^~~~~~~~~~~ can produce an error
^~~~ can produce an error
```

The result of such a chain is either `Ok` of the outcome, or `Err` of the first
error.

While this pattern is highly ergonomic, it does not fit particularly well into
our evolving error story
([interoperation](https://github.com/rust-lang/rfcs/pull/201) or
[try blocks](https://github.com/rust-lang/rfcs/pull/243)), and it is the only
module in `std` to follow this pattern.

Eventually, we would like to write

```rust
File::open(some_path)?.read_to_end()
```

to take advantage of the `FromError` infrastructure, hook into error handling
control flow, and to provide good chaining ergonomics throughout *all* Rust APIs
-- all while keeping this handling a bit more explicit via the `?`
operator. (See https://github.com/rust-lang/rfcs/pull/243 for the rough direction).

In the meantime, this RFC proposes to phase out the use of impls for
`IoResult`. This will require use of `try!` for the time being.

(Note: this may put some additional pressure on at least landing the basic use
of `?` instead of today's `try!` before 1.0 final.)

# Detailed design
[Detailed design]: #detailed-design

There's a lot of material here, so the RFC starts with high-level goals,
principles, and organization, and then works its way through the various modules
involved.

## Vision for IO
[Vision for IO]: #vision-for-io

Rust's IO story had undergone significant evolution, starting from a
`libuv`-style pure green-threaded model to a dual green/native model and now to
a [pure native model](https://github.com/rust-lang/rfcs/pull/230). Given that
history, it's worthwhile to set out explicitly what is, and is not, in scope for
`std::io`

### Goals
[Goals]: #goals

For Rust 1.0, the aim is to:

* Provide a *blocking* API based directly on the services provided by the native
OS for native threads.

These APIs should cover the basics (files, basic networking, basic process

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

Non-blocking I/O is required for correctness in many cases. It's not just about performance, and Rust will not be usable for use cases like process supervision if it doesn't provide it.

This comment has been minimized.

@alexcrichton

alexcrichton Dec 13, 2014

Member

Non-blocking I/O is required for correctness in many cases. It's not just about performance, and Rust will not be usable for use cases like process supervision if it doesn't provide it.

To highlight the start of this RFC:

It is explicitly not a goal at this time to support asynchronous programming models or nonblocking IO, nor is it a goal for the blocking APIs to eventually be used in a nonblocking "mode" or style.

We agree that non-blocking I/O is quite important! We do not, however, have the design bandwidth at this time to stabilize the APIs for 1.0. It's a tough decision to punt on it, but we believe that a solid 1.0 release is more important than stabilizing non-blocking functionality first.

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

I'm not saying that it should be stabilized now. The RFC is making it seem like it's a performance optimization for some niches. It's necessary to write correct code in many cases.

management, etc) and suffice to write servers following the classic Apache
thread-per-connection model. They should impose essentially zero cost over the

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

I find it hard to see how this can be rationalized with the proposal to have hacky lossy abstractions (deadlines, cancellations) instead of using the native functionality. I don't think it's justified at all.

underlying OS services; the core APIs should map down to a single syscall
unless more are needed for cross-platform compatibility.

* Provide basic blocking abstractions and building blocks (various stream and
buffer types and adapters) based on traditional blocking IO models but adapted
to fit well within Rust.

* Provide hooks for integrating with low-level and/or platform-specific APIs.

* Ensure reasonable forwards-compatibility with future async IO models.

It is explicitly *not* a goal at this time to support asynchronous programming
models or nonblocking IO, nor is it a goal for the blocking APIs to eventually
be used in a nonblocking "mode" or style.

Rather, the hope is that the basic abstractions of files, paths, sockets, and so
on will eventually be usable directly within an async IO programing model and/or
with nonblocking APIs. This is the case for most existing languages, which offer
multiple interoperating IO models.

The *long term* intent is certainly to support async IO in some form,
but doing so will require new research and experimentation.

### Design principles
[Design principles]: #design-principles

Now that the scope has been clarified, it's important to lay out some broad
principles for the `io` and `os` modules. Many of these principles are already
being followed to some extent, but this RFC makes them more explicit and applies
them more uniformly.

#### What cross-platform means
[What cross-platform means]: #what-cross-platform-means

Historically, Rust's `std` has always been "cross-platform", but as discussed in
[Posix and libuv bias] this hasn't always played out perfectly. The proposed
policy is below. **With this policies, the APIs should largely feel like part of
"Rust" rather than part of any legacy, and they should enable truly portable
code**.

Except for an explicit opt-in (see [Platform-specific opt-in] below), all APIs
in `std` should be cross-platform:

* The APIs should **only expose a service or a configuration if it is supported on
all platforms**, and if the semantics on those platforms is or can be made
loosely equivalent. (The latter requires exercising some
judgment). Platform-specific functionality can be handled separately
([Platform-specific opt-in]) and interoperate with normal `std` abstractions.

This policy rules out functions like `chown` which have a clear meaning on
Unix and no clear interpretation on Windows; the ownership and permissions
models are *very* different.

* The APIs should **follow Rust's conventions**, including their naming, which
should be platform-neutral.

This policy rules out names like `fstat` that are the legacy of a particular
platform family.

* The APIs should **never directly expose the representation** of underlying
platform types, even if they happen to coincide on the currently-supported
platforms. Cross-platform types in `std` should be newtyped.

This policy rules out exposing e.g. error numbers directly as an integer type.

The next subsection gives detail on what these APIs should look like in relation
to system services.

#### Relation to the system-level APIs
[Relation to the system-level APIs]: #relation-to-the-system-level-apis

How should Rust APIs map into system services? This question breaks down along
several axes which are in tension with one another:

* **Guarantees**. The APIs provided in the mainline `io` modules should be
predominantly safe, aside from the occasional `unsafe` function. In
particular, the representation should be sufficiently hidden that most use
cases are safe by construction. Beyond memory safety, though, the APIs should
strive to provide a clear multithreaded semantics (using the `Send`/`Sync`
kinds), and should use Rust's type system to rule out various kinds of bugs
when it is reasonably ergonomic to do so (following the usual Rust
conventions).

* **Ergonomics**. The APIs should present a Rust view of things, making use of
the trait system, newtypes, and so on to make system services fit well with
the rest of Rust.

* **Abstraction/cost**. On the other hand, the abstractions introduced in `std`
must not induce significant costs over the system services -- or at least,
there must be a way to safely access the services directly without incurring
this penalty. When useful abstractions would impose an extra cost, they must
be pay-as-you-go.

Putting the above bullets together, **the abstractions must be safe, and they
should be as high-level as possible without imposing a tax**.

* **Coverage**. Finally, the `std` APIs should over time strive for full
coverage of non-niche, cross-platform capabilities.

#### Platform-specific opt-in
[Platform-specific opt-in]: #platform-specific-opt-in

Rust is a systems language, and as such it should expose seamless, no/low-cost
access to system services. In many cases, however, this cannot be done in a
cross-platform way, either because a given service is only available on some
platforms, or because providing a cross-platform abstraction over it would be
costly.

This RFC proposes *platform-specific opt-in*: submodules of `os` that are named
by platform, and made available via `#[cfg]` switches. For example, `os::unix`
can provide APIs only available on Unix systems, and `os::linux` can drill
further down into Linux-only APIs. (You could even imagine subdividing by OS
versions.) This is "opt-in" in the sense that, like the `unsafe` keyword, it is
very easy to audit for potential platform-specificity: just search for
`os::anyplatform`. Moreover, by separating out subsets like `linux`, it's clear
exactly how specific the platform dependency is.

The APIs in these submodules are intended to have the same flavor as other `io`
APIs and should interoperate seamlessly with cross-platform types, but:

* They should be named according to the underlying system services when there is
a close correspondence.

* They may reveal the underlying OS type if there is nothing to be gained by
hiding it behind an abstraction.

For example, the `os::unix` module could provide a `stat` function that takes a
standard `Path` and yields a custom struct. More interestingly, `os::linux`
might include an `epoll` function that could operate *directly* on many `io`
types (e.g. various socket types), without any explicit conversion to a file
descriptor; that's what "seamless" means.

Each of the platform modules will offer a custom `prelude` submodule,
intended for glob import, that includes all of the extension traits
applied to standard IO objects.

The precise design of these modules is in the very early stages and will likely
remain `#[unstable]` for some time.

### Proposed organization
[Proposed organization]: #proposed-organization

The `io` module is currently the biggest in `std`, with an entire hierarchy
nested underneath; it mixes general abstractions/tools with specific IO objects.
The `os` module is currently a bit of a dumping ground for facilities that don't
fit into the `io` category.

This RFC proposes the revamp the organization by flattening out the hierarchy
and clarifying the role of each module:

```
std
env environment manipulation
fs file system
io core io abstractions/adapters
prelude the io prelude
net networking
os
unix platform-specific APIs
linux ..
windows ..
os_str platform-sensitive string handling
process process management
```

In particular:

* The contents of `os` will largely move to `env`, a new module for
inspecting and updating the "environment" (including environment variables, CPU
counts, arguments to `main`, and so on).

* The `io` module will include things like `Reader` and `BufferedWriter` --
cross-cutting abstractions that are needed throughout IO.

The `prelude` submodule will export all of the traits and most of the types
for IO-related APIs; a single glob import should suffice to set you up for
working with IO. (Note: this goes hand-in-hand with *removing* the bits of
`io` currently in the prelude, as
[recently proposed](https://github.com/rust-lang/rfcs/pull/503).)

* The root `os` module is used purely to house the platform submodules discussed
[above](#platform-specific-opt-in).

* The `os_str` module is part of the solution to the Unicode problem; see
[String handling] below.

* The `process` module over time will grow to include querying/manipulating
already-running processes, not just spawning them.

## Revising `Reader` and `Writer`
[Revising `Reader` and `Writer`]: #revising-reader-and-writer

> To be added in a follow-up PR.
## String handling
[String handling]: #string-handling

> To be added in a follow-up PR.
## Deadlines
[Deadlines]: #deadlines

> To be added in a follow-up PR.
## Splitting streams and cancellation
[Splitting streams and cancellation]: #splitting-streams-and-cancellation

> To be added in a follow-up PR.
## Modules
[Modules]: #modules

Now that we've covered the core principles and techniques used
throughout IO, we can go on to explore the modules in detail.

### `core::io`
[core::io]: #coreio

> To be added in a follow-up PR.
### The `std::io` facade
[The std::io facade]: #the-stdio-facade

> To be added in a follow-up PR.
### `std::env`
[std::env]: #stdenv

> To be added in a follow-up PR.
### `std::fs`
[std::fs]: #stdfs

> To be added in a follow-up PR.
### `std::net`
[std::net]: #stdnet

> To be added in a follow-up PR.
### `std::process`
[std::process]: #stdprocess

> To be added in a follow-up PR.
### `std::os`
[std::os]: #stdos

Initially, this module will be empty except for the platform-specific
`unix` and `windows` modules. It is expected to grow additional, more

This comment has been minimized.

@erickt

erickt Dec 13, 2014

Will the windows posix functions be exposed in unix?

This comment has been minimized.

@alexcrichton

alexcrichton Dec 13, 2014

Member

Not currently, we do not want to bind ourselves to msvcrt. Similar functionality can be exposed through its own interface at the std::os level if necessary, but we want to only require kernel32.dll on windows.

This comment has been minimized.

@thestinger

thestinger Dec 13, 2014

That would mean replacing calls to functions like malloc and _aligned_malloc too.

This comment has been minimized.

@retep998

retep998 Dec 14, 2014

Member

It's a good thing there is HeapAlloc then. However, so long as we want to maintain compatibility with allocations by C libraries, we'll have to provide malloc.

This comment has been minimized.

@thestinger

thestinger Dec 15, 2014

It can't expose malloc in a crate that's linked by default if it's not going to depend on the CRT.

This comment has been minimized.

@retep998

retep998 Dec 19, 2014

Member

We could just move malloc and friends to an optional crate for interop with external libraries when needed.

specific platform submodules (like `linux`, `macos`) over time.

## Odds and ends
[Odds and ends]: #odds-and-ends

> To be expanded in a follow-up PR.
### The `io` prelude
[The io prelude]: #the-io-prelude

The `prelude` submodule will contain most of the traits, types, and
modules discussed in this RFC; it is meant to provide maximal
convenience when working with IO of any kind. The exact contents of
the module are left as an open question.

# Drawbacks
[Drawbacks]: #drawbacks

This RFC is largely about cleanup, normalization, and stabilization of
our IO libraries -- work that needs to be done, but that also
represents nontrivial churn.

However, the actual implementation work involved is estimated to be
reasonably contained, since all of the functionality is already in
place in some form (including `os_str`, due to @SimonSapin's
[WTF-8 implementation](https://github.com/SimonSapin/rust-wtf8)).

# Alternatives
[Alternatives]: #alternatives

The main alternative design would be to continue staying with the
Posix tradition in terms of naming and functionality (for which there
is precedent in some other languages). However, Rust is already
well-known for its strong cross-platform compatibility in `std`, and
making the library more Windows-friendly will only increase its appeal.

More radically different designs (in terms of different design
principles or visions) are outside the scope of this RFC.

# Unresolved questions
[Unresolved questions]: #unresolved-questions

> To be expanded in a follow-up PR.