`impl fmt::Display` (and `fmt::Debug`) for `Fn(&mut fmt::Formatter) -> fmt::Result`

There are many times when I find it cumbersome to work with fmt::Debug especially, when I want to use the debug_* family of helpers with multi-level output. Currently this involves creating a data structure to wrap part of my struct, implementing fmt::Debug for it, then referring to it in e.g. my DebugStruct::field method call. An example:

// TextLayout is an opaque struct in another crate with a `size`
// method, that does not implement `Debug`.
struct Layout {
    inner: TextLayout,
}

impl Debug for Layouts {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        struct DebugLayout<'a>(&'a TextLayout);
        impl Debug for Layouts {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                f.debug_struct("TextLayout")
                    .field("size", &self.0.size())
                    .finish()
            }
        }

        f.debug_struct("Layout")
            .field("inner", &DebugLayout(&self.inner))
            .finish()
    }
}

I feel like it would be easier if there was something like

impl<F> fmt::Display for F where F: Fn(&mut fmt::Formatter) -> fmt::Result {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        (self)(f)
    }
}

Then I could do

impl Debug for Layout {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("Layout")
            .field("inner", |f| f.debug_struct("TextLayout")
                .field("size", self.inner.size())
                .finish())
            .finish()
    }
}

Which is more concise and easier to read.

Note that you can define a general solution yourself, e.g.

// TextLayout is an opaque struct in another crate with a `size`
// method, that does not implement `Debug`.
struct Layout {
    inner: TextLayout,
}

impl fmt::Debug for Layout {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Layout")
            .field(
                "inner",
                &DebugFn(|f| {
                    f.debug_struct("TextLayout")
                        .field("size", &self.inner.size())
                        .finish()
                }),
            )
            .finish()
    }
}

// generally usable abstraction
pub struct DebugFn<F>(pub F)
where
    F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result;
impl<F> fmt::Debug for DebugFn<F>
where
    F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0(f)
    }
}

I also think that – if anything – the standard library should provide such a wrapper type (or alternatively a function creating it), and not create a general implementation for the closures/functions themself!

1 Like

Yes a wrapper type is another option. If you went down this path, you could have functions like DebugStruct::field_with that did the wrapping for you.

Edit I also don't see any reason why it couldn't support fmt::Display as well (the exact same impl)

Good point. For now, that’s also possible to achieve through an extension crate without needing to change the standard library (yet, the API might be useful enough for eventually being included there as-well).

struct Layout {
    inner: TextLayout,
}

impl fmt::Debug for Layout {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Layout")
            .field_with("inner", |f| {
                f.debug_struct("TextLayout")
                    .field("size", &self.inner.size())
                    .finish()
            })
            .finish()
    }
}

// generally usable abstraction
pub struct DebugFn<F>(pub F)
where
    F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result;
impl<F> fmt::Debug for DebugFn<F>
where
    F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0(f)
    }
}

pub trait DebugStructExt {
    fn field_with<F>(&mut self, name: &str, format_value: F) -> &mut Self
    where
        F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result;
}
impl DebugStructExt for DebugStruct<'_, '_> {
    fn field_with<F>(&mut self, name: &str, format_value: F) -> &mut Self
    where
        F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
    {
        self.field(name, &DebugFn(format_value))
    }
}

I might publish this as a crate for now.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.