Today, fmt::Argments has as_str method which returns &'static str for formats like format_args!("foo"). Internally it has pieces: &'a [&'static str], and for what I work on it would really save me a lot of memory and resources if I would be able to store those pieces as &'static str instead of formatting into a buffer in my core::fmt::Write implementation.
What do you think can be done about it? First thing that comes to my mind is add fn write_static_str(&mut self, s: &'static str) { self.write_str(s) } method to core::fmt::Write trait that will by default forward to already existing write_str method. Then, code inside write_fmt will call write_static_str whenever the element of pieces slice gets outputted
Today, this will be optimal (will give 2 &'static str to the fmt::Write implementor):
write!(f, "foo")?;
write!(f, "{}", x)?;
write!(f, "bar"?;
While this will not give any &'static str to user code:
write!(f, "foo{}bar", x)?;
We can make it optimal without any API changes by modifying this function:
#[stable(feature = "rust1", since = "1.0.0")]
pub fn write(output: &mut dyn Write, args: Arguments<'_>) -> Result {
let mut formatter = Formatter::new(output, FormattingOptions::new());
let mut idx = 0;
match args.fmt {
None => {
// We can use default formatting parameters for all arguments.
for (i, arg) in args.args.iter().enumerate() {
// SAFETY: args.args and args.pieces come from the same Arguments,
// which guarantees the indexes are always within bounds.
let piece = unsafe { args.pieces.get_unchecked(i) };
if !piece.is_empty() {
formatter.buf.write_str(*piece)?;
}
// SAFETY: There are no formatting parameters and hence no
// count arguments.
unsafe {
arg.fmt(&mut formatter)?;
}
idx += 1;
}
}
Some(fmt) => {
// Every spec has a corresponding argument that is preceded by
// a string piece.
for (i, arg) in fmt.iter().enumerate() {
// SAFETY: fmt and args.pieces come from the same Arguments,
// which guarantees the indexes are always within bounds.
let piece = unsafe { args.pieces.get_unchecked(i) };
if !piece.is_empty() {
formatter.buf.write_str(*piece)?;
}
// SAFETY: arg and args.args come from the same Arguments,
// which guarantees the indexes are always within bounds.
unsafe { run(&mut formatter, arg, args.args) }?;
idx += 1;
}
}
}
// There can be only one trailing string piece left.
if let Some(piece) = args.pieces.get(idx) {
formatter.buf.write_str(*piece)?;
}
Ok(())
}
Instead of calling write_str(*piece) construct fmt::Arguments with only this piece and call write_fmt. By default write_fmt has the check for as_str so the behavior will not change in this case at all.
There is a playground link to help you understand the issue. Ideally, together will give the same output as split does. Code inside core::fmt::write is perfectly aware that piece is &'static str, but is using write_str, removing that information. Thus either write_static_str could be added or new fmt::Arguments with only that piece should be constructed inside core::fmt::Write and calling write_fmt with it instead of write_str (no api changes, not sure about perf though. llvm should be able to optimize it away, but there is dyn).