Skip to content

Add File constructors that nudge people toward buffered I/O #446

Closed
@cuviper

Description

@cuviper

Proposal

Problem statement

Using unbuffered I/O is a common performance footgun which has users asking why their Rust program is so slow.

Motivating examples or use cases

Issue #445 opened this discussion, "Attempt to help users avoid unbuffered small reads":

Users regularly come to the /r/rust subreddit looking for help when their Rust code doesn't perform as well as they expect. The second most common culprit (after debug vs release build) is doing unbuffered small reads from a file.

I'm wondering if there's anything we could do to help users catch or avert this more easily.

Solution sketch

Add two File constructors that wrap the result in buffers:

impl File {
    pub fn open_buffered<P: AsRef<Path>>(path: P) -> io::Result<io::BufReader<File>> {
        File::open(path).map(io::BufReader::new)
    }

    pub fn create_buffered<P: AsRef<Path>>(path: P) -> io::Result<io::BufWriter<File>> {
        File::create(path).map(io::BufWriter::new)
    }
}

These are trivial, but the main advantage is that such explicit methods will appear in auto-complete lists. Hopefully that will raise more programmer awareness that the basic open and create methods are not buffered, by omission, so they may consider if that's something they want.

Users with deeper OpenOptions will still need to decide which kind of buffering makes sense in their case. Similarly, if we want create_new_buffered, we would probably choose BufWriter, whereas create_new is opened in read-write mode.

Alternatives

  • The status quo is that File documents this:

    File does not buffer reads and writes. For efficiency, consider wrapping the file in a BufReader or BufWriter when performing many small read or write calls, unless unbuffered reads and writes are required.

    We could deem that sufficient.

  • Attempt to help users avoid unbuffered small reads #445 has several other ideas like debug assertions, new lints, or cargo tooling for performance footguns. Any of those could still be implemented in addition to this proposal.

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ACP-acceptedAPI Change Proposal is accepted (seconded with no objections)T-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions