From b083d5d294f591ae4dbb349a3aef8a3f6b2acf90 Mon Sep 17 00:00:00 2001 From: tobias-wilfert Date: Wed, 12 Aug 2026 09:49:34 +0200 Subject: [PATCH 1/2] add `format_into` --- src/lib.rs | 46 ++++++++++++++++++++++++++++++++++----- tests/test_format_into.rs | 21 ++++++++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 tests/test_format_into.rs diff --git a/src/lib.rs b/src/lib.rs index 13711b8..e479e7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -663,19 +663,53 @@ pub trait Format<'f> { return Ok(Cow::Borrowed(format)); } - let mut access = ArgumentAccess::new(arguments); let mut buffer = Vec::with_capacity(format.len()); + self.format_into(&mut buffer, format, arguments)?; + + Ok(Cow::Owned(unsafe { String::from_utf8_unchecked(buffer) })) + } + + /// Formats the given string with the specified arguments and writes the result into the given writer. + /// + /// Individual arguments must implement [`Debug`] and [`serde::Serialize`]. The arguments + /// container must implement the [`FormatArgs`] trait. + /// + /// ```rust + /// use dynfmt::{Format, NoopFormat}; + /// + /// let mut buffer = Vec::new(); + /// NoopFormat.format_into(&mut buffer, "hello, world", &["unused"]).expect("formatting failed"); + /// assert_eq!(b"hello, world", buffer.as_slice()); + /// ``` + /// + /// [`Debug`]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html + /// [`serde::Serialize`]: https://docs.rs/serde/latest/serde/trait.Serialize.html + /// [`FormatArgs`]: trait.FormatArgs.html + fn format_into( + &self, + mut write: W, + format: &'f str, + arguments: A, + ) -> Result<(), Error<'f>> + where + W: io::Write, + A: FormatArgs, + { + let mut access = ArgumentAccess::new(arguments); let mut last_match = 0; - for spec in iter { + for spec in self.iter_args(format)? { let spec = spec?; - buffer.extend(format[last_match..spec.start()].as_bytes()); - spec.format_into(&mut buffer, &mut access)?; + write + .write_all(format[last_match..spec.start()].as_bytes()) + .map_err(Error::Io)?; + spec.format_into(&mut write, &mut access)?; last_match = spec.end(); } - buffer.extend(format[last_match..].as_bytes()); - Ok(Cow::Owned(unsafe { String::from_utf8_unchecked(buffer) })) + write + .write_all(format[last_match..].as_bytes()) + .map_err(Error::Io) } } diff --git a/tests/test_format_into.rs b/tests/test_format_into.rs new file mode 100644 index 0000000..682d97b --- /dev/null +++ b/tests/test_format_into.rs @@ -0,0 +1,21 @@ +#![cfg(feature = "python")] + +use std::io::ErrorKind; + +use dynfmt::{Error, Format, PythonFormat}; + +#[test] +fn writes_formatted_output() { + let mut buffer = Vec::new(); + PythonFormat + .format_into(&mut buffer, "hello, %s!", &["world"]) + .expect("formatting failed"); + assert_eq!(b"hello, world!", buffer.as_slice()); +} + +#[test] +fn aborts_on_full_writer() { + let mut buffer = [0u8; 8]; + let result = PythonFormat.format_into(&mut buffer.as_mut_slice(), "hello, %s!", &["world"]); + assert!(matches!(result.unwrap_err(), Error::BadData(..))); +} From e8a6e882621a61696537ff6621cad7bfad23ce3e Mon Sep 17 00:00:00 2001 From: Jan Michael Auer Date: Wed, 12 Aug 2026 14:56:11 +0200 Subject: [PATCH 2/2] Fix clippy::needless_borrows_for_generic_args in test_format_into The stricter clippy config merged from master flags the unnecessary & borrows on array literals and an unused import. --- tests/test_format_into.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_format_into.rs b/tests/test_format_into.rs index 682d97b..c539e98 100644 --- a/tests/test_format_into.rs +++ b/tests/test_format_into.rs @@ -1,14 +1,12 @@ #![cfg(feature = "python")] -use std::io::ErrorKind; - use dynfmt::{Error, Format, PythonFormat}; #[test] fn writes_formatted_output() { let mut buffer = Vec::new(); PythonFormat - .format_into(&mut buffer, "hello, %s!", &["world"]) + .format_into(&mut buffer, "hello, %s!", ["world"]) .expect("formatting failed"); assert_eq!(b"hello, world!", buffer.as_slice()); } @@ -16,6 +14,6 @@ fn writes_formatted_output() { #[test] fn aborts_on_full_writer() { let mut buffer = [0u8; 8]; - let result = PythonFormat.format_into(&mut buffer.as_mut_slice(), "hello, %s!", &["world"]); + let result = PythonFormat.format_into(buffer.as_mut_slice(), "hello, %s!", ["world"]); assert!(matches!(result.unwrap_err(), Error::BadData(..))); }