Write/Format problem

I'm working on a crate annotate-snippets. In it, I need to format text to produce a String, and also optionally style it using one of potentially many "stylesheets".

The challenge I encountered comes from the fact that write! and formatter::write_fmt require a literal for the pattern, so I'm not sure how to turn:

write!(f, "[{}]", some_str);

into:

trait Style {
  fn format(f: Formatter, pattern: &str, value: &str) {
     write!(f, pattern, value);
  }
}

impl Style for ColorStyle {
  fn format(f: Formatter, pattern: &str, value: &str) {
    write!(f, "..."); // color set to red
    write!(f, pattern, value);
    write!(f, "..."); // color reset
  }
}

How can I achieve it?

The abstraction used by the macros is the Arguments; whenever you want to output that to a Writeable you use the .write_fmt() method.

For a user, providing an Arguments is done through the format_args! macro. Since interacting your Style::format() through an inner format_args! macro will be cumbersome for users, you can then abstract that with your own macro :smile: :

use ::core::fmt;

pub
trait Style {
    fn style (fmt_args: fmt::Arguments)
      -> String
    ;
}

pub
struct Red;

impl Style for Red {
    fn style (fmt_args: fmt::Arguments)
      -> String
    {
        format!("[color=red]{}[/color]", fmt_args)
    }
}

fn main ()
{
    println!("{}", Red::style(format_args!("Hello, {}!", "World")));
    
    macro_rules! style {(
        $Red:ty, for $($fmt:tt)*
    ) => (
        <$Red as $crate::Style>::style(format_args!($($fmt)*))
    )}
    
    println!("{}", style!(Red, for "Hello, {}!", "World"));
}

Just to offer a generalization of the fmt_args solution, fmt::Arguments implements Display, and thus if you make the function argument this type instead: fn style(args: &Display) then you accept both a &format_args!(..) (with & this time, unfortunately) and also many other types like string literals or references to other values.

1 Like

Here is a version using the "impl Display into &dyn Display" suggested by @bluss, and with those ugly intermediate Strings (and thus heap-allocations) removed:

Click to see the code
use ::core::fmt;

/// Since we cannot have `fn(...)` signatures with generic (lifetimes) involved
/// we use Box<dyn Fn(...) -> _ + 'static> with the idea to only use
/// capture-less closures, which will be equivalent to a `&'static fn(...) -> _`
type Printer = Box<
    dyn
        Fn(&(dyn fmt::Display + '_), &mut fmt::Formatter<'_>) -> fmt::Result +
        'static +
>;

pub
struct StyleFmt<Displayable : fmt::Display> {
    printer: Printer,
    displayable: Displayable,
}

impl<Displayable : fmt::Display> fmt::Display
    for StyleFmt<Displayable>
{
    #[inline]
    fn fmt (self: &'_ Self, stream: &'_ mut fmt::Formatter<'_>)
      -> fmt::Result
    {
        (self.printer)(&self.displayable, stream)
    }
}

pub
trait Style {
    fn style<Displayable : fmt::Display> (displayable: Displayable)
      -> StyleFmt<Displayable>
    ;
}

pub
struct Red;

impl Style for Red {
    fn style<Displayable : fmt::Display> (displayable: Displayable)
      -> StyleFmt<Displayable>
    {
        StyleFmt {
            displayable,
            printer: Box::new(|displayable, stream| {
                write!(stream, "[color=red]{}[/color]", displayable)
            }), /* given that the closure is zero-sized,
                this Box<dyn ...> is more like a `&'static fn(...)`
                in particular: there is no heap allocation */
        }
    }
}

Thank you so much! I was able to incorporate the suggestion and it works!

1 Like