about summary refs log tree commit diff
path: root/library/std/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-09-25 04:57:12 +0000
committerbors <bors@rust-lang.org>2024-09-25 04:57:12 +0000
commit4c62024cd5dbafb64941bded422e4fbc2a100e73 (patch)
treed53288325071e253910a6b5ffbdef798a43a08a1 /library/std/src
parent1b5aa96d6016bafe50e071b45d4d2e3c90fd766f (diff)
parent458537ebc0e7d893103e03450f8830061bab1b2d (diff)
downloadrust-4c62024cd5dbafb64941bded422e4fbc2a100e73.tar.gz
rust-4c62024cd5dbafb64941bded422e4fbc2a100e73.zip
Auto merge of #130803 - cuviper:file-buffered, r=joshtriplett
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: https://github.com/rust-lang/libs-team/issues/446
Tracking Issue: #130804
Diffstat (limited to 'library/std/src')
-rw-r--r--library/std/src/fs.rs77
-rw-r--r--library/std/src/io/buffered/bufreader.rs8
-rw-r--r--library/std/src/io/buffered/bufreader/buffer.rs12
-rw-r--r--library/std/src/io/buffered/bufwriter.rs10
-rw-r--r--library/std/src/lib.rs1
-rw-r--r--library/std/src/sys/pal/unix/thread.rs4
6 files changed, 109 insertions, 3 deletions
diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs
index 55f3b628ce8..db7867337dd 100644
--- a/library/std/src/fs.rs
+++ b/library/std/src/fs.rs
@@ -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,
@@ -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
diff --git a/library/std/src/io/buffered/bufreader.rs b/library/std/src/io/buffered/bufreader.rs
index e51dde994de..fcb3e36027b 100644
--- a/library/std/src/io/buffered/bufreader.rs
+++ b/library/std/src/io/buffered/bufreader.rs
@@ -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
diff --git a/library/std/src/io/buffered/bufreader/buffer.rs b/library/std/src/io/buffered/bufreader/buffer.rs
index 1bf84d8bef3..3df7e3971da 100644
--- a/library/std/src/io/buffered/bufreader/buffer.rs
+++ b/library/std/src/io/buffered/bufreader/buffer.rs
@@ -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 {
@@ -37,6 +37,16 @@ impl Buffer {
     }
 
     #[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
         // that region is initialized because those are all invariants of this type.
diff --git a/library/std/src/io/buffered/bufwriter.rs b/library/std/src/io/buffered/bufwriter.rs
index 13516d3b961..c41bae2aa4e 100644
--- a/library/std/src/io/buffered/bufwriter.rs
+++ b/library/std/src/io/buffered/bufwriter.rs
@@ -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
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index 4d93af6ea65..b81e7c18abb 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -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
 //
diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs
index 7fe9b6c3e52..ce5e8ea5866 100644
--- a/library/std/src/sys/pal/unix/thread.rs
+++ b/library/std/src/sys/pal/unix/thread.rs
@@ -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;
@@ -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();