#[macro_export]
macro_rules! forward_display {
(<$($generic:ident),+> in $this:ty => $field:ident) => {
impl <$($generic: ::core::fmt::Display),+> ::core::fmt::Display for $this {
fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
::core::fmt::Display::fmt(&self.$field, fmt)
}
}
};
(<$($generic:ident),+> in $this:ty) => {
impl <$($generic: ::core::fmt::Display),+> ::core::fmt::Display for $this {
fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
::core::fmt::Display::fmt(&self.0, fmt)
}
}
};
($ty:ty) => {
impl ::core::fmt::Display for $ty {
fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
::core::fmt::Display::fmt(&self.0, fmt)
}
}
};
($ty:ty => $field:ident) => {
impl ::core::fmt::Display for $ty {
fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
::core::fmt::Display::fmt(&self.$field, fmt)
}
}
};
}
#[macro_export]
macro_rules! impl_display {
($ty:ty: $format:literal) => {
impl ::core::fmt::Display for $ty {
fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
::core::write!(fmt, $format)
}
}
};
($ty:ty: $format:literal, $($args:expr),+) => {
impl ::core::fmt::Display for $ty {
fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
::core::write!(fmt, $format, $($args),+)
}
}
};
($ty:ty: $format:literal, $($args:expr),+ ,) => {
$crate::impl_display!($ty: $format, $($args),+);
};
}
#[macro_export]
macro_rules! impl_display_enum {
(
$ty:ty:
$(
$variant:ident
$( ( $($tuple:tt)* ) )?
$( { $($named:tt)* } )?
=> $output:tt
),+
$(,)?
) => {
impl ::core::fmt::Display for $ty {
fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self {
$(
Self::$variant
$( ( $($tuple)* ) )?
$( { $($named)* } )?
=> $crate::impl_display_enum!(
@write fmt
[
$( ( $($tuple)* ) )?
$( { $($named)* } )?
]
$output
),
)+
}
}
}
};
(
@write $fmt:ident
[]
$text:literal
) => {
$fmt.write_str($text)
};
(
@write $fmt:ident
[ $($fields:tt)* ]
$format:literal
) => {
::core::write!($fmt, $format)
};
(
@write $fmt:ident
[ $($fields:tt)* ]
( $format:literal $($args:tt)* )
) => {
::core::write!($fmt, $format $($args)*)
};
}
#[cfg(test)]
mod tests {
use alloc::{
borrow::ToOwned as _,
string::{String, ToString as _},
};
#[test]
fn impl_forward_for_newtype_struct() {
struct Foo(String);
forward_display!(Foo);
assert_eq!(Foo("hello world".to_owned()).to_string(), "hello world");
}
#[test]
fn impl_forward_newtype_named_struct() {
struct Foo {
inner: u64,
}
forward_display!(Foo => inner);
assert_eq!(Foo { inner: 42 }.to_string(), "42");
}
#[test]
fn impl_forward_generic_newtype_struct() {
struct Foo<T>(T);
forward_display!(<T> in Foo<T>);
assert_eq!(Foo(42).to_string(), "42");
}
#[test]
fn impl_forward_generic_named_struct() {
struct Foo<T> {
inner: T,
}
forward_display!(<T> in Foo<T> => inner);
assert_eq!(Foo { inner: 42 }.to_string(), "42");
}
#[test]
fn impl_basic_for_unit_struct() {
struct Foo;
impl_display!(Foo: "foo");
assert_eq!(Foo.to_string(), "foo");
}
#[test]
fn impl_basic_with_args() {
struct Foo;
impl_display!(Foo: "foo {} {}", 2, 3);
assert_eq!(Foo.to_string(), "foo 2 3");
}
#[test]
fn impl_basic_with_inline_args() {
const HI: &str = "hello";
struct Hello3;
impl_display!(Hello3: "{HI} world");
assert_eq!(Hello3.to_string(), "hello world");
}
#[test]
fn impl_enum_named_variant_with_trailing_comma() {
enum Foo {
Bar { value: u64 },
}
impl_display_enum!(Foo: Bar { value } => "{value}",);
assert_eq!(Foo::Bar { value: 42 }.to_string(), "42");
}
#[test]
fn enum_mixed_patterns_and_format_arguments() {
#[allow(dead_code)] enum Event {
Idle,
Message(&'static str, bool),
Progress { completed: usize, total: usize },
Items(&'static [u8]),
Done,
}
impl_display_enum! {
Event:
Idle => "{{idle}}",
Message(msg, ..) => "{{{msg}}}",
Progress { completed: count, .. } => "{count} complete",
Items(items) => ("{} items ({label})", items.len(), label = "ready",),
Done => ("done: {}", 42),
}
assert_eq!(Event::Idle.to_string(), "{{idle}}");
assert_eq!(Event::Message("hi", true).to_string(), "{hi}");
assert_eq!(
Event::Progress {
completed: 2,
total: 3
}
.to_string(),
"2 complete"
);
assert_eq!(Event::Items(&[1, 2]).to_string(), "2 items (ready)");
assert_eq!(Event::Done.to_string(), "done: 42");
}
#[test]
fn enum_format_arguments_without_trailing_comma() {
#[allow(dead_code)] enum Value {
Tuple(u8, u8),
Named { value: u8 },
}
impl_display_enum!(Value:
Tuple(_, value) => "{value:02x}",
Named { value } => ("{number:02x}", number = value)
);
assert_eq!(Value::Tuple(0, 10).to_string(), "0a");
assert_eq!(Value::Named { value: 11 }.to_string(), "0b");
enum Count {
Items(&'static [u8]),
}
impl_display_enum!(Count: Items(items) => ("{}", items.len()));
assert_eq!(Count::Items(&[1, 2]).to_string(), "2");
}
#[test]
fn enum_grouped_unit_string_supports_captures() {
const LABEL: &str = "ready";
enum State {
Bare,
Grouped,
}
impl_display_enum!(State:
Bare => "{LABEL}",
Grouped => ("{LABEL}"),
);
assert_eq!(State::Bare.to_string(), "{LABEL}");
assert_eq!(State::Grouped.to_string(), "ready");
}
#[test]
fn enum_propagates_format_errors() {
struct Fails;
impl core::fmt::Display for Fails {
fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Err(core::fmt::Error)
}
}
enum Value {
Tuple(Fails),
Named { value: Fails },
}
impl_display_enum!(Value: Tuple(value) => "{value}", Named { value } => "{value}");
let mut output = String::new();
core::fmt::write(&mut output, format_args!("{}", Value::Tuple(Fails))).unwrap_err();
core::fmt::write(
&mut output,
format_args!("{}", Value::Named { value: Fails }),
)
.unwrap_err();
}
}