impl_more/
display.rs

1/// Implements [`Display`] for structs by forwarding to one of its field.
2///
3/// Emitted code is not compatible with `#[no_std]`.
4///
5/// Newtype structs can omit the field identifier.
6///
7/// # Examples
8///
9/// For newtype struct:
10///
11/// ```
12/// # use impl_more::forward_display;
13/// struct Foo(String);
14///
15/// impl_more::forward_display!(Foo);
16///
17/// assert_eq!(Foo("hello world".to_owned()).to_string(), "hello world");
18/// ```
19///
20/// For struct with named field:
21///
22/// ```
23/// # use impl_more::forward_display;
24/// struct Bar {
25///     inner: u64,
26/// }
27///
28/// impl_more::forward_display!(Bar => inner);
29///
30/// assert_eq!(Bar { inner: 42 }.to_string(), "42");
31/// ```
32///
33/// For generic newtype struct (note that `Display` bounds are applied to all type parameters):
34///
35/// ```
36/// # use impl_more::forward_display;
37/// struct Baz<T>(T);
38///
39/// impl_more::forward_display!(<T> in Baz<T>);
40///
41/// assert_eq!(Baz(42u64).to_string(), "42");
42/// ```
43///
44/// [`Display`]: std::fmt::Display
45#[macro_export]
46macro_rules! forward_display {
47    (<$($generic:ident),+> in $this:ty => $field:ident) => {
48        impl <$($generic: ::core::fmt::Display),+> ::core::fmt::Display for $this {
49            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
50                ::core::fmt::Display::fmt(&self.$field, fmt)
51            }
52        }
53    };
54
55    (<$($generic:ident),+> in $this:ty) => {
56        impl <$($generic: ::core::fmt::Display),+> ::core::fmt::Display for $this {
57            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
58                ::core::fmt::Display::fmt(&self.0, fmt)
59            }
60        }
61    };
62
63    ($ty:ty) => {
64        impl ::core::fmt::Display for $ty {
65            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
66                ::core::fmt::Display::fmt(&self.0, fmt)
67            }
68        }
69    };
70
71    ($ty:ty => $field:ident) => {
72        impl ::core::fmt::Display for $ty {
73            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
74                ::core::fmt::Display::fmt(&self.$field, fmt)
75            }
76        }
77    };
78}
79
80/// Implements [`Display`] for structs using a `format!`-like string constructor.
81///
82/// # Examples
83///
84/// Display implementation can be just a string literal.
85///
86/// ```
87/// # use impl_more::forward_display;
88/// struct Hello;
89/// impl_more::impl_display!(Foo; "hello world");
90/// assert_eq!(Hello.to_string(), "hello world");
91/// ```
92///
93/// Explicit and inline format args are supported.
94///
95/// ```
96/// # use impl_more::forward_display;
97/// struct Hello2;
98/// impl_more::impl_display!(Foo; "hello world {}", 2);
99/// assert_eq!(Hello2.to_string(), "hello world 2");
100///
101/// const HI: &str = "hello"
102///
103/// struct Hello3;
104/// impl_more::impl_display!(Foo; "{HI} world");
105/// assert_eq!(Foo.to_string(), "hello world");
106/// ```
107///
108/// [`Display`]: std::fmt::Display
109#[macro_export]
110macro_rules! impl_display {
111    // no format args
112    ($ty:ty; $format:literal) => {
113        impl ::core::fmt::Display for $ty {
114            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
115                ::core::write!(fmt, $format)
116            }
117        }
118    };
119
120    // with explicit format args
121    ($ty:ty; $format:literal, $($args:expr),+) => {
122        impl ::core::fmt::Display for $ty {
123            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
124                ::core::write!(fmt, $format, $($args),+)
125            }
126        }
127    };
128
129    // strip trailing comma and forward to format args branch
130    ($ty:ty; $format:literal, $($args:expr),+ ,) => {
131        $crate::impl_display!($ty; $format, $($args),+);
132    };
133}
134
135/// Implements [`Display`] for enums using a static string or format args for each variant.
136///
137/// # Examples
138///
139/// ```
140/// # extern crate alloc;
141/// use impl_more::impl_display_enum;
142///
143/// enum Foo {
144///     Bar,
145///     Qux,
146/// }
147///
148/// impl_display_enum!(Foo, Bar => "bar", Qux => "qux");
149///
150/// assert_eq!(Foo::Bar.to_string(), "bar");
151/// assert_eq!(Foo::Qux.to_string(), "qux");
152///
153/// enum CoordOrMsg {
154///     Coord(i64, i64),
155///     Msg(&'static str),
156/// }
157///
158/// impl_display_enum!(CoordOrMsg, Coord(x, y) => "{x}, {y}", Msg(msg) => "message: {msg}");
159///
160/// assert_eq!(CoordOrMsg::Coord(4, 2).to_string(), "4, 2");
161/// assert_eq!(CoordOrMsg::Msg("hi").to_string(), "message: hi");
162/// ```
163///
164/// [`Display`]: std::fmt::Display
165#[macro_export]
166macro_rules! impl_display_enum {
167    ($ty:ty, $($variant:ident => $stringified:literal),+) => {
168        impl ::core::fmt::Display for $ty {
169            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
170                fmt.write_str(match self {
171                    $(
172                        Self::$variant => $stringified,
173                    )*
174                })
175            }
176        }
177    };
178
179    ($ty:ty, $($variant:ident => $stringified:literal),+ ,) => {
180        $crate::impl_display_enum!($ty, $($variant => $stringified),+);
181    };
182
183    ($ty:ty, $($variant:ident ($($inner:ident),+) => $format:literal),+) => {
184        impl ::core::fmt::Display for $ty {
185            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
186                use ::core::fmt::Write as _;
187
188                // a more efficient method (format_args) is blocked by:
189                // https://github.com/rust-lang/rust/issues/15023
190                let mut buf = ::std::string::String::new();
191
192                match self {
193                    $(
194                        Self::$variant($($inner),+) =>
195                            ::core::write!(&mut buf, $format, $($inner = $inner),+)?,
196                    )*
197                };
198
199                fmt.write_str(&buf)
200            }
201        }
202    };
203
204    ($ty:ty, $($variant:ident ($($inner:ident),+) => $format:literal),+ ,) => {
205        $crate::impl_display_enum!($ty, $($variant ($($inner),+) => $format),+);
206    };
207
208    ($ty:ty, $($variant:ident { $($inner:ident),+ } => $format:literal),+) => {
209        impl ::core::fmt::Display for $ty {
210            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
211                use ::core::fmt::Write as _;
212
213                // a more efficient method (format_args) is blocked by:
214                // https://github.com/rust-lang/rust/issues/15023
215                let mut buf = ::std::string::String::new();
216
217                match self {
218                    $(
219                        Self::$variant { $($inner),+ } =>
220                            ::core::write!(&mut buf, $format, $($inner = $inner),+)?,
221                    )*
222                };
223
224                fmt.write_str(&buf)
225            }
226        }
227    };
228
229    ($ty:ty, $($variant:ident { $($inner:ident),+ } => $format:literal),+ ,) => {
230        $crate::impl_display_enum!($ty, $($variant ($($inner),+) => $format),+);
231    };
232
233    // TODO: mixed named and positional variant support
234}
235
236#[cfg(test)]
237mod tests {
238    use alloc::{
239        borrow::ToOwned as _,
240        string::{String, ToString as _},
241    };
242
243    #[test]
244    fn impl_forward_for_newtype_struct() {
245        struct Foo(String);
246
247        forward_display!(Foo);
248
249        assert_eq!(Foo("hello world".to_owned()).to_string(), "hello world");
250    }
251
252    #[test]
253    fn impl_forward_newtype_named_struct() {
254        struct Foo {
255            inner: u64,
256        }
257
258        forward_display!(Foo => inner);
259
260        assert_eq!(Foo { inner: 42 }.to_string(), "42");
261    }
262
263    #[test]
264    fn impl_forward_generic_newtype_struct() {
265        struct Foo<T>(T);
266
267        forward_display!(<T> in Foo<T>);
268
269        assert_eq!(Foo(42).to_string(), "42");
270    }
271
272    #[test]
273    fn impl_forward_generic_named_struct() {
274        struct Foo<T> {
275            inner: T,
276        }
277
278        forward_display!(<T> in Foo<T> => inner);
279
280        assert_eq!(Foo { inner: 42 }.to_string(), "42");
281    }
282
283    #[test]
284    fn impl_basic_for_unit_struct() {
285        struct Foo;
286        impl_display!(Foo; "foo");
287        assert_eq!(Foo.to_string(), "foo");
288    }
289
290    #[test]
291    fn impl_basic_with_args() {
292        struct Foo;
293        impl_display!(Foo; "foo {} {}", 2, 3);
294        assert_eq!(Foo.to_string(), "foo 2 3");
295    }
296
297    #[rustversion::stable(1.58)]
298    #[test]
299    fn impl_basic_with_inline_args() {
300        const HI: &str = "hello";
301
302        struct Hello3;
303        impl_display!(Hello3; "{HI} world");
304        assert_eq!(Hello3.to_string(), "hello world");
305    }
306}