Skip to content

Commit 49e36c0

Browse files
committed
Reserve before write_fmt for owned buffers
Reserve before formatting with `fmt::Arguments::estimated_capacity()` in `fmt::Write::write_fmt` and `io::Write::write_fmt` implementations for owned buffer types. Adding `#[inline]` to `write_fmt` shows minor perf regressions, so leave it off like the default impl.
1 parent 8c39296 commit 49e36c0

File tree

6 files changed

+100
-37
lines changed

6 files changed

+100
-37
lines changed

library/alloc/src/string.rs

+9
Original file line numberDiff line numberDiff line change
@@ -3186,6 +3186,15 @@ impl fmt::Write for String {
31863186
self.push(c);
31873187
Ok(())
31883188
}
3189+
3190+
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
3191+
if let Some(s) = args.as_statically_known_str() {
3192+
self.write_str(s)
3193+
} else {
3194+
self.reserve(args.estimated_capacity());
3195+
fmt::write(self, args)
3196+
}
3197+
}
31893198
}
31903199

31913200
/// An iterator over the [`char`]s of a string.

library/core/src/fmt/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -710,9 +710,10 @@ impl<'a> Arguments<'a> {
710710
}
711711

712712
/// Same as [`Arguments::as_str`], but will only return `Some(s)` if it can be determined at compile time.
713+
#[unstable(feature = "fmt_internals", reason = "internal to standard library", issue = "none")]
713714
#[must_use]
714715
#[inline]
715-
fn as_statically_known_str(&self) -> Option<&'static str> {
716+
pub fn as_statically_known_str(&self) -> Option<&'static str> {
716717
let s = self.as_str();
717718
if core::intrinsics::is_val_statically_known(s.is_some()) { s } else { None }
718719
}

library/std/src/ffi/os_str.rs

+10
Original file line numberDiff line numberDiff line change
@@ -753,10 +753,20 @@ impl Hash for OsString {
753753

754754
#[stable(feature = "os_string_fmt_write", since = "1.64.0")]
755755
impl fmt::Write for OsString {
756+
#[inline]
756757
fn write_str(&mut self, s: &str) -> fmt::Result {
757758
self.push(s);
758759
Ok(())
759760
}
761+
762+
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
763+
if let Some(s) = args.as_statically_known_str() {
764+
self.write_str(s)
765+
} else {
766+
self.reserve(args.estimated_capacity());
767+
fmt::write(self, args)
768+
}
769+
}
760770
}
761771

762772
impl OsStr {

library/std/src/io/cursor.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
mod tests;
33

44
use crate::alloc::Allocator;
5-
use crate::cmp;
65
use crate::io::prelude::*;
76
use crate::io::{self, BorrowedCursor, ErrorKind, IoSlice, IoSliceMut, SeekFrom};
7+
use crate::{cmp, fmt};
88

99
/// A `Cursor` wraps an in-memory buffer and provides it with a
1010
/// [`Seek`] implementation.
@@ -603,6 +603,15 @@ where
603603
true
604604
}
605605

606+
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> {
607+
if let Some(s) = args.as_statically_known_str() {
608+
self.write_all(s.as_bytes())
609+
} else {
610+
self.inner.reserve(args.estimated_capacity());
611+
io::default_write_fmt(self, args)
612+
}
613+
}
614+
606615
#[inline]
607616
fn flush(&mut self) -> io::Result<()> {
608617
Ok(())
@@ -627,6 +636,15 @@ where
627636
true
628637
}
629638

639+
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> {
640+
if let Some(s) = args.as_statically_known_str() {
641+
self.write_all(s.as_bytes())
642+
} else {
643+
self.inner.reserve(args.estimated_capacity());
644+
io::default_write_fmt(self, args)
645+
}
646+
}
647+
630648
#[inline]
631649
fn flush(&mut self) -> io::Result<()> {
632650
Ok(())

library/std/src/io/impls.rs

+18
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,15 @@ impl<A: Allocator> Write for Vec<u8, A> {
495495
Ok(())
496496
}
497497

498+
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> {
499+
if let Some(s) = args.as_statically_known_str() {
500+
self.write_all(s.as_bytes())
501+
} else {
502+
self.reserve(args.estimated_capacity());
503+
io::default_write_fmt(self, args)
504+
}
505+
}
506+
498507
#[inline]
499508
fn flush(&mut self) -> io::Result<()> {
500509
Ok(())
@@ -638,6 +647,15 @@ impl<A: Allocator> Write for VecDeque<u8, A> {
638647
Ok(())
639648
}
640649

650+
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> {
651+
if let Some(s) = args.as_statically_known_str() {
652+
self.write_all(s.as_bytes())
653+
} else {
654+
self.reserve(args.estimated_capacity());
655+
io::default_write_fmt(self, args)
656+
}
657+
}
658+
641659
#[inline]
642660
fn flush(&mut self) -> io::Result<()> {
643661
Ok(())

library/std/src/io/mod.rs

+42-35
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,47 @@ pub(crate) fn default_read_buf_exact<R: Read + ?Sized>(
612612
Ok(())
613613
}
614614

615+
pub(crate) fn default_write_fmt<W: Write + ?Sized>(
616+
this: &mut W,
617+
fmt: fmt::Arguments<'_>,
618+
) -> Result<()> {
619+
// Create a shim which translates a `Write` to a `fmt::Write` and saves off
620+
// I/O errors, instead of discarding them.
621+
struct Adapter<'a, T: ?Sized + 'a> {
622+
inner: &'a mut T,
623+
error: Result<()>,
624+
}
625+
626+
impl<T: Write + ?Sized> fmt::Write for Adapter<'_, T> {
627+
fn write_str(&mut self, s: &str) -> fmt::Result {
628+
match self.inner.write_all(s.as_bytes()) {
629+
Ok(()) => Ok(()),
630+
Err(e) => {
631+
self.error = Err(e);
632+
Err(fmt::Error)
633+
}
634+
}
635+
}
636+
}
637+
638+
let mut output = Adapter { inner: this, error: Ok(()) };
639+
match fmt::write(&mut output, fmt) {
640+
Ok(()) => Ok(()),
641+
Err(..) => {
642+
// Check whether the error came from the underlying `Write`.
643+
if output.error.is_err() {
644+
output.error
645+
} else {
646+
// This shouldn't happen: the underlying stream did not error,
647+
// but somehow the formatter still errored?
648+
panic!(
649+
"a formatting trait implementation returned an error when the underlying stream did not"
650+
);
651+
}
652+
}
653+
}
654+
}
655+
615656
/// The `Read` trait allows for reading bytes from a source.
616657
///
617658
/// Implementors of the `Read` trait are called 'readers'.
@@ -1867,41 +1908,7 @@ pub trait Write {
18671908
/// ```
18681909
#[stable(feature = "rust1", since = "1.0.0")]
18691910
fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<()> {
1870-
// Create a shim which translates a Write to a fmt::Write and saves
1871-
// off I/O errors. instead of discarding them
1872-
struct Adapter<'a, T: ?Sized + 'a> {
1873-
inner: &'a mut T,
1874-
error: Result<()>,
1875-
}
1876-
1877-
impl<T: Write + ?Sized> fmt::Write for Adapter<'_, T> {
1878-
fn write_str(&mut self, s: &str) -> fmt::Result {
1879-
match self.inner.write_all(s.as_bytes()) {
1880-
Ok(()) => Ok(()),
1881-
Err(e) => {
1882-
self.error = Err(e);
1883-
Err(fmt::Error)
1884-
}
1885-
}
1886-
}
1887-
}
1888-
1889-
let mut output = Adapter { inner: self, error: Ok(()) };
1890-
match fmt::write(&mut output, fmt) {
1891-
Ok(()) => Ok(()),
1892-
Err(..) => {
1893-
// check if the error came from the underlying `Write` or not
1894-
if output.error.is_err() {
1895-
output.error
1896-
} else {
1897-
// This shouldn't happen: the underlying stream did not error, but somehow
1898-
// the formatter still errored?
1899-
panic!(
1900-
"a formatting trait implementation returned an error when the underlying stream did not"
1901-
);
1902-
}
1903-
}
1904-
}
1911+
default_write_fmt(self, fmt)
19051912
}
19061913

19071914
/// Creates a "by reference" adapter for this instance of `Write`.

0 commit comments

Comments
 (0)