How to organize chain processing in Rust?

I created a trait like:

pub trait Colorized : std::fmt::Display {
    fn green(&self) -> ??? {
    }
    fn bright(&self) -> ??? {
    }
....

So, I want to organize a chain of calls to the trait, for example:

println!("Colorized {}", message.green().bright());

Certainly I can implement the trait for str, but if I return str, then I will lose color attributes I set before. So an idea is to return ColorAttributeHolder. More likely, I will need to implement Colorized for it too, to be able to chain my calls. However I have few questions:

  1. where and how store the original caller of Colorized?
  2. how to pass Colorized to the next chained calls? The best is by a reference, but the first self and the next self can be different and again I will lose valuable information.

Probably, my approach is wrong, so please share your approach.

I would not create a general purpose wrapper like ColorAttributeHolder, it does not compose easily (and effiiciently, at least not without specialization).

instead, I would create wrappers for individual decorative attributes, something like:

struct Green<T>(T);
struct Bright<T>(T);
//...

impl<T> Display for Green<T> where T: Display {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        write!(f, "\x1b[32m{}\x1b[0m", self.0)
    }
}
//...

note these wrappers have no memory overhead, the attributes are encoded in the types. then, the Colorized trait can return these wrapper types (with a blanket implementation):

trait Colorized {
    fn green(&self) -> Green<&Self> { Green(self) }
    fn bright(&self) -> Bright<&Self> { Bright(self) }
}

impl<T> Colorized for T where T: Display {}

I'd use a dedicated struct to represent the respective color formatting:

The reason is, that the chained decorators above have weaker invariants. I.e. I could™ do something like text.green().red().blue() and may get surprising results. A dedicated formatter class can ensure that there's always just one color, which cannot be overwritten.
I.e. a call like the one above will not compile.

However, there's already a crate for this:

Indeed, it's an interesting approach, however I need to digest it more to generate a valuable result.

This approach I like more, it's more understandable for me. Regarding multiple definitions of a color, it's a basic problem of any CLI interface, what , for example, tail should use if you defined '-n 5 -n 1000'? Generally there are three resolutions:

  1. use first definition
  2. use last definition
  3. generate an error

I prefer the variant 2.
Here is my current code:

use std::fmt;
#[derive(Default, Debug, Clone)] 
pub enum Color {
    Red,
    Yellow,
    Blue,
    Green,
    Magenta,
    Cyan,
    Black,
    White,
    #[default]
    System,
}
#[derive(Default, Debug, Clone)] 
pub struct ColorHolder{///<'a> {
    fg: Color,
    bg: Color,
    bright: bool,
    bright_bg: bool,
    bold: bool,
    italic: bool,
    value: String,
}
pub trait Colorized : std::fmt::Display {
   
    fn blue(&self) -> ColorHolder {
          ColorHolder{fg:Color::Blue,bg:Color::default(),value:self.to_string(),..Default::default()}
    }
    fn bright(&self) -> ColorHolder {
          ColorHolder{bright:true,value:self.to_string(),..Default::default()}
    }
    fn on_black(&self) -> ColorHolder {
          ColorHolder{bg:Color::Black,value:self.to_string(),..Default::default()}
    }
}
impl Colorized for ColorHolder {
    fn blue(&self) -> ColorHolder {
        let mut res = self.clone();
        res.fg = Color::Blue;
        res
    }
    fn on_black(&self) -> ColorHolder {
        let mut res = self.clone();
        res.bg = Color::Black;
        res
    }
    fn bright(&self) -> ColorHolder {
        let mut res = self.clone();
        res.bright = true;
        res
    }
}
impl fmt::Display for ColorHolder {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut color = String::new();
        match self.fg {
            Color::Green => color.push_str("32;"),
            Color::Blue => if self.bright {color.push_str("94;")} else {color.push_str("34;")},
            _ => todo!("impl soon")
        }
        match self.bg {
            Color::Green => color.push_str("42;"),
            Color::Blue => color.push_str("44;"),
            Color::Black => color.push_str("40;"),
            _ => todo!("impl soon")
        }
        if color.is_empty() {
            write!(f, "{}", self.value)
        } else {
            write!(f, "\x1b[{color}m{}\x1b[0m", self.value)
        }
    }
}
impl Colorized for str {}
impl Colorized for String {}
  

Can you suggest an approach to eliminate exceeding cloning?

PS Great thanks to the author of the colored. I hope he is lurking in the forum, so I have a chance to say him hello, and thanks for the awesome work.

pass data by value rather than ref

impl Colorized for ColorHolder {
    fn blue(mut self) -> ColorHolder {
        self.fg = Color::Blue;
        self
    }
    fn on_black(mut self) -> ColorHolder {
       self.bg = Color::Black;
        self
    }
    fn bright(mut self) -> ColorHolder {
        self.bright = true
        self
    }
}

clone only when you actually want to also keep the old value

this will also generally be compiled to change the data inplace and the compiler can ofthen track the whole chain to make it a single action so it's also very efficent

It's a good remark, I will use it. I still struggle to use a ref or move data. Probably, I will move for now, and switch to a ref later, if some needs will be there.

I think your approach is better than used in the Colored. I implemented it and it took just a half of a day. I have a completely license free crate after, with a very small footprint. I think it's worth to ask here, then use crates.io, again YMMV.