Suggestion for helper for writing `fmt::Debug` impls with nested structure

Motivation

When I'm writing debug impls, I sometimes want to simulate some structure that is different from the structure of the actual contents of the struct/enum. For example, say I have a 2D path representation like the SVG path (details not important), but I use a custom encoding to save space. In the debug impl I want to show something like SVGPath { elements: [<elements here>] }. Currently the way I would do this would to be to create a wrapper struct for the inner data, implement Debug on that, and then do f.debug_struct("SVGPath").field("elements", &MyCustomStruct(&self.data)).finish(), which is all very verbose.

I'd like to be able to solve the above problem using a closure, so I can do f.debug_struct("SVGPath").field("elements", |f| f.debug_list().entries(&self.data).finish()).finish(), but currently Debug isn't implemented for Fn(&mut fmt::Formatter) -> fmt::Result.

Proposal

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

// Or if you'd prefer not to do a blanket fn implementation, we could provide a wrapper at a 
// small cost to ergonomics.

struct DebugFn<'a, F>(pub &'a F);

impl<'a, F> Debug for DebugFn<'a, F>
where F: Fn(&mut fmt::Formatter) -> fmt::Result 
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        (self.0)(f)
    }
}

Another option would be to offer just a helper function, without the need for the larger API in the form of a struct. I’m imagining:

use std::fmt;

pub fn display_with(f: impl Fn(&mut fmt::Formatter) -> fmt::Result) -> impl fmt::Display {
    struct DisplayWith<F>(F);

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

    DisplayWith(f)
}

pub fn debug_with(f: impl Fn(&mut fmt::Formatter) -> fmt::Result) -> impl fmt::Debug {
    struct DebugWith<F>(F);

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

    DebugWith(f)
}

[The display version can be useful for functions that want to return some form of impl Display value (which some API may prefer over generating a String).]

2 Likes

I think I prefer your suggestion - it feels like it should be a function rather than a struct. Thanks.

I've written code that looks like what you have there a lot of times :slight_smile:

1 Like

I sometimes wonder whether we don’t perhaps want a feature where we can write something like

fn demo() -> impl Debug {
    Debug {
        fmt: |f| f.write_str("wow!"),
    }
}

(click here to read the rest; I don’t want to distract from this thread too much with long off-topics deliberations)

In this world, you could also write

let mut n = 0;
let iterator1 = move Iterator<Item = i32> {
    next: || {
        n += 1;
        Some(n)
    },
};

return iterator1;

or

let mut n = 0;
let iterator2 = Iterator<Item = i32> {
    next: || {
        n += 1;
        Some(n)
    },
    size_hint: || (usize::MAX, None),
};

for n in iterator2 {
    if random() {
        break;
    }
}

or

fn range(mut low: i32, high: i32) -> impl Iterator<Item = i32> {
    assert!(low <= high);
    move Iterator<Item = i32> {
        next: || (low < high).then(|| {
            let item = low;
            low += 1;
            item
        }),
        size_hint: || {
            match (high - low).try_into() {
                Ok(s) => (s, Some(s)),
                Err(_) => (usize::MAX, None),
            }
        }
    }
}

Such ad-hoc anonymous trait implementations would be a lot like closures (but not actually the same, just the syntax I came up with is inspired by closures) – but the killer feature of this would be that multiple methods can have mutable access to captured variables, see e.g. the range function above, (which means this is more capable than simple helper functions taking multiple closures, on for each method). Another great feature would be that this could support convenient variable capture despite implementing generic methods. (As given that generic closures do not exist in Rust.)

How to handle super traits? How to name parameter of a generic method? I don’t know… but a MVP probably doesn’t necessarily need to address all this, anyways.

Sorry if this is taking it somewhat off-topic as it’s more of a language feature proposal, going far beyond (both in scope and time-frame) a simple library addition. (Also I haven’t cross-checked at all against potential existing proposals.)

1 Like

I RFC-ed that a while ago:

https://github.com/rust-lang/rfcs/pull/2604

1 Like

That resembles a lot Java anonymous classes.

This was postponed, so.. there is a chance?

I very much enjoyed your RFC fwiw.

1 Like

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