Skip to content

Commit

Permalink
Auto merge of rust-lang#130803 - cuviper:file-buffered, r=joshtriplett
Browse files Browse the repository at this point in the history
Add `File` constructors that return files wrapped with a buffer

In addition to the light convenience, these are intended to raise visibility that buffering is something you should consider when opening a file, since unbuffered I/O is a common performance footgun to Rust newcomers.

ACP: rust-lang/libs-team#446
Tracking Issue: rust-lang#130804
  • Loading branch information
bors committed Sep 25, 2024
2 parents 2c408b1 + 2ab86f0 commit 4a7aeff
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 7 deletions.
77 changes: 77 additions & 0 deletions std/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,44 @@ impl File {
OpenOptions::new().read(true).open(path.as_ref())
}

/// Attempts to open a file in read-only mode with buffering.
///
/// See the [`OpenOptions::open`] method, the [`BufReader`][io::BufReader] type,
/// and the [`BufRead`][io::BufRead] trait for more details.
///
/// If you only need to read the entire file contents,
/// consider [`std::fs::read()`][self::read] or
/// [`std::fs::read_to_string()`][self::read_to_string] instead.
///
/// # Errors
///
/// This function will return an error if `path` does not already exist.
/// Other errors may also be returned according to [`OpenOptions::open`].
///
/// # Examples
///
/// ```no_run
/// #![feature(file_buffered)]
/// use std::fs::File;
/// use std::io::BufRead;
///
/// fn main() -> std::io::Result<()> {
/// let mut f = File::open_buffered("foo.txt")?;
/// assert!(f.capacity() > 0);
/// for (line, i) in f.lines().zip(1..) {
/// println!("{i:6}: {}", line?);
/// }
/// Ok(())
/// }
/// ```
#[unstable(feature = "file_buffered", issue = "130804")]
pub fn open_buffered<P: AsRef<Path>>(path: P) -> io::Result<io::BufReader<File>> {
// Allocate the buffer *first* so we don't affect the filesystem otherwise.
let buffer = io::BufReader::<Self>::try_new_buffer()?;
let file = File::open(path)?;
Ok(io::BufReader::with_buffer(file, buffer))
}

/// Opens a file in write-only mode.
///
/// This function will create a file if it does not exist,
Expand Down Expand Up @@ -404,6 +442,45 @@ impl File {
OpenOptions::new().write(true).create(true).truncate(true).open(path.as_ref())
}

/// Opens a file in write-only mode with buffering.
///
/// This function will create a file if it does not exist,
/// and will truncate it if it does.
///
/// Depending on the platform, this function may fail if the
/// full directory path does not exist.
///
/// See the [`OpenOptions::open`] method and the
/// [`BufWriter`][io::BufWriter] type for more details.
///
/// See also [`std::fs::write()`][self::write] for a simple function to
/// create a file with some given data.
///
/// # Examples
///
/// ```no_run
/// #![feature(file_buffered)]
/// use std::fs::File;
/// use std::io::Write;
///
/// fn main() -> std::io::Result<()> {
/// let mut f = File::create_buffered("foo.txt")?;
/// assert!(f.capacity() > 0);
/// for i in 0..100 {
/// writeln!(&mut f, "{i}")?;
/// }
/// f.flush()?;
/// Ok(())
/// }
/// ```
#[unstable(feature = "file_buffered", issue = "130804")]
pub fn create_buffered<P: AsRef<Path>>(path: P) -> io::Result<io::BufWriter<File>> {
// Allocate the buffer *first* so we don't affect the filesystem otherwise.
let buffer = io::BufWriter::<Self>::try_new_buffer()?;
let file = File::create(path)?;
Ok(io::BufWriter::with_buffer(file, buffer))
}

/// Creates a new file in read-write mode; error if the file exists.
///
/// This function will create a file if it does not exist, or return an error if it does. This
Expand Down
8 changes: 8 additions & 0 deletions std/src/io/buffered/bufreader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ impl<R: Read> BufReader<R> {
BufReader::with_capacity(DEFAULT_BUF_SIZE, inner)
}

pub(crate) fn try_new_buffer() -> io::Result<Buffer> {
Buffer::try_with_capacity(DEFAULT_BUF_SIZE)
}

pub(crate) fn with_buffer(inner: R, buf: Buffer) -> Self {
Self { inner, buf }
}

/// Creates a new `BufReader<R>` with the specified buffer capacity.
///
/// # Examples
Expand Down
12 changes: 11 additions & 1 deletion std/src/io/buffered/bufreader/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! without encountering any runtime bounds checks.
use crate::cmp;
use crate::io::{self, BorrowedBuf, Read};
use crate::io::{self, BorrowedBuf, ErrorKind, Read};
use crate::mem::MaybeUninit;

pub struct Buffer {
Expand All @@ -36,6 +36,16 @@ impl Buffer {
Self { buf, pos: 0, filled: 0, initialized: 0 }
}

#[inline]
pub fn try_with_capacity(capacity: usize) -> io::Result<Self> {
match Box::try_new_uninit_slice(capacity) {
Ok(buf) => Ok(Self { buf, pos: 0, filled: 0, initialized: 0 }),
Err(_) => {
Err(io::const_io_error!(ErrorKind::OutOfMemory, "failed to allocate read buffer"))
}
}
}

#[inline]
pub fn buffer(&self) -> &[u8] {
// SAFETY: self.pos and self.cap are valid, and self.cap => self.pos, and
Expand Down
10 changes: 10 additions & 0 deletions std/src/io/buffered/bufwriter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ impl<W: Write> BufWriter<W> {
BufWriter::with_capacity(DEFAULT_BUF_SIZE, inner)
}

pub(crate) fn try_new_buffer() -> io::Result<Vec<u8>> {
Vec::try_with_capacity(DEFAULT_BUF_SIZE).map_err(|_| {
io::const_io_error!(ErrorKind::OutOfMemory, "failed to allocate write buffer")
})
}

pub(crate) fn with_buffer(inner: W, buf: Vec<u8>) -> Self {
Self { inner, buf, panicked: false }
}

/// Creates a new `BufWriter<W>` with at least the specified buffer capacity.
///
/// # Examples
Expand Down
1 change: 1 addition & 0 deletions std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@
#![feature(slice_concat_trait)]
#![feature(thin_box)]
#![feature(try_reserve_kind)]
#![feature(try_with_capacity)]
#![feature(vec_into_raw_parts)]
// tidy-alphabetical-end
//
Expand Down
4 changes: 2 additions & 2 deletions std/src/sys/pal/unix/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ mod cgroups {
use crate::borrow::Cow;
use crate::ffi::OsString;
use crate::fs::{File, exists};
use crate::io::{BufRead, BufReader, Read};
use crate::io::{BufRead, Read};
use crate::os::unix::ffi::OsStringExt;
use crate::path::{Path, PathBuf};
use crate::str::from_utf8;
Expand Down Expand Up @@ -690,7 +690,7 @@ mod cgroups {
/// If the cgroupfs is a bind mount then `group_path` is adjusted to skip
/// over the already-included prefix
fn find_mountpoint(group_path: &Path) -> Option<(Cow<'static, str>, &Path)> {
let mut reader = BufReader::new(File::open("/proc/self/mountinfo").ok()?);
let mut reader = File::open_buffered("/proc/self/mountinfo").ok()?;
let mut line = String::with_capacity(256);
loop {
line.clear();
Expand Down
1 change: 1 addition & 0 deletions test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#![doc(test(attr(deny(warnings))))]
#![doc(rust_logo)]
#![feature(rustdoc_internals)]
#![feature(file_buffered)]
#![feature(internal_output_capture)]
#![feature(staged_api)]
#![feature(process_exitcode_internals)]
Expand Down
6 changes: 2 additions & 4 deletions test/src/term/terminfo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::io::{self, BufReader};
use std::path::Path;
use std::{env, error, fmt};
use std::{env, error, fmt, io};

use parm::{Param, Variables, expand};
use parser::compiled::{msys_terminfo, parse};
Expand Down Expand Up @@ -102,8 +101,7 @@ impl TermInfo {
}
// Keep the metadata small
fn _from_path(path: &Path) -> Result<TermInfo, Error> {
let file = File::open(path).map_err(Error::IoError)?;
let mut reader = BufReader::new(file);
let mut reader = File::open_buffered(path).map_err(Error::IoError)?;
parse(&mut reader, false).map_err(Error::MalformedTerminfo)
}
}
Expand Down

0 comments on commit 4a7aeff

Please sign in to comment.