Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to achieve conditional nested formatting without multiple allocations?

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?

like image 513
phip1611 Avatar asked Apr 28 '26 11:04

phip1611


1 Answers

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.

like image 89
Masklinn Avatar answered Apr 30 '26 02:04

Masklinn



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!