`fmt::Arguments` expose `pieces`. Maybe add `write_static_str` to `Write`?

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).