String formatting

Hello,
I am currently writing a formatted for a custom string formatter. In Rust, I was expecting to have lazy generator, and then calling to_string() or equivalent to actually do the work, and do as few allocation as possible (ie each string generated by the generator would be appended directly into the final string).

pub trait TextFormatter {                                                                                              
    fn bold(s: &str) -> String;                                                                                        
    fn strike(s: &str) -> String;                                                                                      
    fn normal(s: &str) -> String;                                                                                      
}                                                                                                                      
                                                                                                                       
pub fn format<T:>(some_boolean: bool, my_vec: Vec<i32>, _formatter: T)
-> String
where T: TextFormatter,
{                       
    let mut out: String = String::new();                                                                               
                                                                                                                       
    out += &format!("Some boolean: {}\n", some_boolean);                                                               
    out += &format!("A comma separated list with some formatting: {}\n",                                           
        my_vec.iter().map(|&value|                                                                                     
            if value == 1  || value == 10 {                                                                            
                T::bold(&value.to_string())                                                                            
            } else if value <= 5 {                                                                                     
                T::strike(&value.to_string())                                                                          
            } else {                                                                                                   
                T::normal(&value.to_string())                                                                          
            })                                                                                                         
        .collect::<Vec<String>>()                                                                                      
        .join(", "));      

    out
}                                                                                           
                                                                                                                       
pub struct MarkdownFormatter;                                                                                          
impl TextFormatter for MarkdownFormatter {                                                                             
    fn bold(s: &str) -> String {                                                                                       
        format!("**{}**", s)                                                                                           
    }                                                                                                                  
    fn strike(s: &str) -> String {                                                                                     
        format!("~~*{}*~~", s)                                                                                         
    }                                                                                                                  
    fn normal(s: &str) -> String {                                                                                     
        format!("{}", s)                                                                                               
    }                                                                                                                  
}

How can I remove most (all?) allocations? Ideally all methods of TextFormatter (including converting the input as strings), I would not need to collect the comma separated list into a Vec before calling format!, and finally if the format function itself could be lazy, it could be perfect.

I can change the whole architecture, the only thing I want to keep is the trait TextFormatter with the 3 functions, but their types can change.

Instead of format!, you can use the write! macro. Here's an example of what that might look like:

use std::fmt::Write;

pub trait TextFormatter {                                                                                              
    fn bold<W: Write>(w: W, s: &str);                                                                                        
    fn strike<W: Write>(w: W, s: &str);                                                                                        
    fn normal<W: Write>(w: W, s: &str);                                                                                        
}                                                                                                                      

pub fn format<T:>(some_boolean: bool, my_vec: Vec<i32>, _formatter: T)
-> String
where T: TextFormatter,
{                       
    let mut out: String = String::new();                                                                               

    writeln!(&mut out, "Some boolean: {}", some_boolean);                                                               
    
    out += "A comma separated list with some formatting: ";
    for (i, &value) in my_vec.iter().enumerate() {
        if i > 0 {
            out += ", ";
        }
        if value == 1  || value == 10 {                                                                            
            T::bold(&mut out, &value.to_string())
        } else if value <= 5 {
            T::strike(&mut out, &value.to_string())                                                                          
        } else {
            T::normal(&mut out, &value.to_string())                                                                          
        }
    }
    out += "\n";

    out
}                                                                                           

pub struct MarkdownFormatter;                                                                                          
impl TextFormatter for MarkdownFormatter {                                                                             
    fn bold<W: Write>(mut w: W, s: &str) {                                         
        write!(w, "**{}**", s);                                                                                           
    }                                                                                                                  
    fn strike<W: Write>(mut w: W, s: &str) {                                                                                     
        write!(w, "~~*{}*~~", s);                                                                                         
    }                                                                                                                  
    fn normal<W: Write>(mut w: W, s: &str) {
        write!(w, "{}", s);                                                                                               
    }                                                                                                                  
}
1 Like

That's absolutely perfect! Thanks a lot!

Is the for loop with indices required? Rust doesn't have something like write_from_iterator(my_vec.iter().map(|value| ...) ?

There's something similar in DebugList but it isn't really customizable enough to use outside of its narrow purpose. I don't think there's a more general solution in libstd.

The Itertools::format method might be useful.

1 Like

I look look all of that. Thanks!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.