I have a format string consisting of multiple conditional components and I'm looking for a solution that doesn't need multiple allocations of Strings for the intermediate steps. If I create each single component of the final format string with the format!-macro then it works but I need an allocation for each component.
I tried experimenting with using only macros to generate the complex format string and its arguments. However, this always resulted in "temporary value is freed at the end of this statement" errors. I tried to use one single buffer of type impl core::fmt::Write but I couldn't make success with this either.
On a high level, I want something like this:
fn main() {
let prefix_include_a = true;
let prefix_include_b = true;
// prefix itself is a formatted string and it is further formatted here
println!("{prefix:<10}{message:>10}!",
prefix = format_prefix(prefix_include_a, prefix_include_b),
message = "message"
);
}
// formats the prefix component of the final string.
// needs multiple String allocations as `format!` is used
fn format_prefix(inc_a: bool, inc_b: bool) -> String {
format!("[{a:<5}{b:<5}]",
a = if inc_a {
format!("{:.1}", 1.234)
} else {
format!("")
},
b = if inc_b {
format!("{:.2}", 1.234)
} else {
format!("")
},
)
}
Is this possible with no or only one single allocation?
The simplest solution is to just write! directly to the underlying stream e.g.
use std::io::{stdout, Write};
fn main() {
let prefix_include_a = true;
let prefix_include_b = true;
let mut stdout = stdout();
let _ = format_prefix(&mut stdout, prefix_include_a, prefix_include_b);
let _ = write!(stdout, "{:>10}", "message");
}
// formats the prefix component of the final string.
// needs multiple String allocations as `format!` is used
fn format_prefix(mut s: impl Write, inc_a: bool, inc_b: bool) -> std::io::Result<()> {
write!(s, "[")?;
if inc_a {
write!(s, "{:<5.1}", 1.234)?;
} else {
write!(s, " ")?;
}
if inc_b {
write!(s, "{:<5.2}", 1.234)?;
} else {
write!(s, " ")?;
}
write!(s, "]")?;
Ok(())
}
An alternative is to reify prefix into a type, and implement Display for it. I would think (hope?) the formatter is a passthrough to the underlying stream, though I've never actually looked:
use std::io::{stdout, Write};
fn main() {
let prefix_include_a = true;
let prefix_include_b = true;
println!(
"{prefix:<10}{message:>10}!",
prefix = Prefix(prefix_include_a, prefix_include_b),
message = "message"
);
}
struct Prefix(bool, bool);
impl std::fmt::Display for Prefix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[")?;
if self.0 {
write!(f, "{:<5.1}", 1.234)?;
} else {
write!(f, " ")?;
}
if self.1 {
write!(f, "{:<5.2}", 1.234)?;
} else {
write!(f, " ")?;
}
write!(f, "]")?;
Ok(())
}
}
Note: I've not handled the padding of the prefix in either version, though I don't think it makes much sense: both prefix values are padded to 5, so the prefix is always at least 12 wide. Padding to 10 makes no sense.
The prefix object could, however, use the externally specified padding to distribute to internal paddings, if that's desirable. See std::fmt::Formatter for information you can obtain about formatting specifiers.
To clean up the conditionals, you could probably use format_args!, though I've pretty little experience with that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With