Here's a technique I've used which is heavy on trait declaration boilerplate, but should reduce boilerplate for trait implementation if you're doing multiple trait impls. It uses one struct per trait method, and one default method per implemented method to turn the "fmt-style" method into a method returning something which implements Display
mod formatter {
use std::fmt;
use std::fmt::{Display, Formatter};
use std::marker::PhantomData;
pub struct TextFmtDisplayBold<TF: ?Sized, T> {
value: T,
phantom: PhantomData<TF>,
}
pub struct TextFmtDisplayStrike<TF: ?Sized, T> {
value: T,
phantom: PhantomData<TF>,
}
pub trait TextFormatter<T: Display> {
fn write_bold(f: &mut Formatter<'_>, value: &T) -> fmt::Result;
fn bold(value: T) -> TextFmtDisplayBold<Self, T> {
TextFmtDisplayBold {
value,
phantom: PhantomData,
}
}
fn write_strike(f: &mut Formatter<'_>, value: &T) -> fmt::Result;
fn strike(value: T) -> TextFmtDisplayBold<Self, T> {
TextFmtDisplayBold {
value,
phantom: PhantomData,
}
}
}
impl<TF, T> Display for TextFmtDisplayBold<TF, T>
where
T: Display,
TF: TextFormatter<T>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
TF::write_bold(f, &self.value)
}
}
impl<TF, T> Display for TextFmtDisplayStrike<TF, T>
where
T: Display,
TF: TextFormatter<T>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
TF::write_strike(f, &self.value)
}
}
// trait implementation is just:
pub struct MarkdownFormatter<T: Display>(PhantomData<T>);
impl<T: Display> TextFormatter<T> for MarkdownFormatter<T> {
fn write_bold(f: &mut Formatter<'_>, value: &T) -> fmt::Result {
write!(f, "**{}**", value)
}
fn write_strike(f: &mut Formatter<'_>, value: &T) -> fmt::Result {
write!(f, "~~{}~~", value)
}
}
}
The usage is identical to the usage of the code in your last post.
Since there are no associated types now, we can also remove the trait's type parameter:
mod formatter {
use std::fmt;
use std::fmt::{Display, Formatter};
use std::marker::PhantomData;
pub struct TextFmtDisplayBold<TF: ?Sized, T> {
value: T,
phantom: PhantomData<TF>,
}
pub struct TextFmtDisplayStrike<TF: ?Sized, T> {
value: T,
phantom: PhantomData<TF>,
}
pub trait TextFormatter {
fn write_bold<T: Display>(f: &mut Formatter<'_>, value: &T) -> fmt::Result;
fn bold<T: Display>(value: T) -> TextFmtDisplayBold<Self, T> {
TextFmtDisplayBold {
value,
phantom: PhantomData,
}
}
fn write_strike<T: Display>(f: &mut Formatter<'_>, value: &T) -> fmt::Result;
fn strike<T: Display>(value: T) -> TextFmtDisplayBold<Self, T> {
TextFmtDisplayBold {
value,
phantom: PhantomData,
}
}
}
impl<TF, T> Display for TextFmtDisplayBold<TF, T>
where
T: Display,
TF: TextFormatter,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
TF::write_bold(f, &self.value)
}
}
impl<TF, T> Display for TextFmtDisplayStrike<TF, T>
where
T: Display,
TF: TextFormatter,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
TF::write_strike(f, &self.value)
}
}
// trait implementation is just:
pub struct MarkdownFormatter;
impl TextFormatter for MarkdownFormatter {
fn write_bold<T: Display>(f: &mut Formatter<'_>, value: &T) -> fmt::Result {
write!(f, "**{}**", value)
}
fn write_strike<T: Display>(f: &mut Formatter<'_>, value: &T) -> fmt::Result {
write!(f, "~~{}~~", value)
}
}
}
Let me know if this works! I've only used it a few times, but it should function. I don't really know how to describe the hack - I think it mostly just bypasses the problem. But it should be just as general?