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