When trait in Rust
I will start this article by demonstrating the problem that I was facing.
I am developing a game in Ratatui, a really nice library for creating UIs in the terminal (TUI).
Ratatui offers a builder pattern for widgets.
Step 1: Building a simple widget
// area: Rect, buf: &mut Buffer
Paragraph::new("Hello There!")
.block(Block::default().borders(Borders::ALL)
.title("Game Menu").title_alignment(Center)
.white())
.light_green()
.alignment(Center)
.render(area, buf);The resulting render:

As you can see, the text is drawn in green, centered, with a white box around it and a title that is also centered.
Now let's do something more complicated: I want to display this widget differently based on whether it is currently "active". Let's replace all the colors with something darker, if the menu is not active.
Step 2: Dynamic Colors
let title_color = if menu_is_active {
Color::White
} else {
Color::DarkGray
};
let text_color = if menu_is_active {
Color::LightCyan
} else {
Color::DarkGray
};
Paragraph::new("Hello There!")
.block(Block::default().borders(Borders::ALL)
.title("Game Menu").title_alignment(Center)
.style(Style::default().fg(title_color)))
.style(Style::default().fg(text_color))
.alignment(Center)
.render(area, buf);Active vs Inactive:


It works, but the code blew up quite a bit.
Step 3: Using the dim() method
As it turns out, there is already a dim() method for widgets, that automatically takes care of dimming all the colors inside of it.
With this, I can get rid of the color logic. I can store the widget in an intermediate variable, and then apply a conditional action on it.
let widget = Paragraph::new("Hello There!")
.block(Block::default().borders(Borders::ALL)
.title("Game Menu").title_alignment(Center)
.white())
.light_green()
.alignment(Center);
let widget = if menu_is_active { widget.dim() } else { widget };
widget.render(area, buf);But this code ruins the fluid, elegant code style we had originally, because I need to perform a conditional transformation on the widget.
Step 4: When Trait
Can we somehow turn an if expression into a chainable method? Yes we can!
I added this trait to my utils file:
pub trait When {
fn when(self, condition: bool, action: impl FnOnce(Self) -> Self) -> Self where Self: Sized;
}
impl<T> When for T {
fn when(self, condition: bool, action: impl FnOnce(T) -> T) -> Self {
if condition {
action(self)
} else {
self
}
}
}And now I can write my widget like this:
use crate::utils::When; // make the trait available
Paragraph::new("Hello There!")
.block(Block::default().borders(Borders::ALL)
.title("Game Menu").title_alignment(Center)
.white())
.light_green()
.alignment(Center)
.when(! menu_is_active, |w| w.dim()) // <---- the good stuff
.render(area, buf);Isn't that glorious?
Let's add another when() and give our widget a Double border when it is active:
Paragraph::new("Hello There!")
.block(Block::default().borders(Borders::ALL)
.when(menu_is_active, |b| b.border_type(Double)) // <---- the good stuff
.title("Game Menu").title_alignment(Center)
.white())
.light_green()
.alignment(Center)
.when(! menu_is_active, |w| w.dim()) // <---- the good stuff
.render(area, buf);The result:
