Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support as = "..." annotation #16

Open
connec opened this issue Jul 27, 2021 · 3 comments
Open

Support as = "..." annotation #16

connec opened this issue Jul 27, 2021 · 3 comments

Comments

@connec
Copy link

connec commented Jul 27, 2021

I've started using custom_debug to reduce the amount of hand-written Debug boilerplate in the codebases I work with. It's especially useful when there's, e.g., a single field in a large enum that you want to exclude, where otherwise you would have to rewrite the Debug logic for the whole enum, or at least the whole variant.

However, one thing I'm struggling with is the proliferation of fmt_* methods I end up with, for the different formatting possibilities. This is exacerbated by the fact that the with = "..." annotation doesn't 'compose' well with other crates like hex_fmt, nor with the format = "..." annotation.

I think a solution to this could be to introduce an as = "..." annotation, which would de-sugar thusly:

#[derive(custom_debug::Debug)]
struct MyType {
    #[debug(as = "hex_fmt::HexFmt")]
    foo: [u8; 32],
}

// generated impl

impl fmt::Debug for MyType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("MyType")
            .field("foo", &hex_fmt::HexFmt(&self.foo))
            .finish()
    }
}

It could even compose with format = "...":

#[derive(custom_debug::Debug)]
struct MyType {
    #[debug(as = "hex_fmt::HexFmt", format = "{:6?}")]
    foo: [u8; 32],
}

// generated impl

impl fmt::Debug for MyType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("MyType")
            .field("foo", &format_args!("{:6?}", hex_fmt::HexFmt(&self.foo)))
            .finish()
    }
}

The value given to as = "..." for a field of type T would have to refer to a function with signature fn(&T) -> impl fmt::Debug.

I'm not very familiar with derive macros, but if this is something you'd be open to adding I could look to create a PR.

@panicbit
Copy link
Owner

Thank you for the detailed issue / feature description!
I will probably look into this within a couple of hours.
Feel free to bump the issue in case I have not reported back within the next days 😄.

@panicbit
Copy link
Owner

panicbit commented Jul 27, 2021

I wonder if there's an even more general mechanism to provide custom debug formatting.
I am thinking about something like:

#[derive(custom_debug::Debug)]
struct MyType {
    #[debug(value = "hex_fmt::HexFmt(self.foo)")]
    foo: [u8; 32],
}

Which would desugar to the same code as you showed, but it would allow convenient, arbitrary wrapping or modification of the value. It would even cover the functionality of #[debug(format = "{:6?}")] via #[debug(value = r#"format_args!("{:6?}", self.foo)"#)].
What do you think? Does #[debug(value = "hex_fmt::HexFmt(self.foo)")] look reasonable to you?

@connec
Copy link
Author

connec commented Jul 28, 2021

I'm a bit wary of writing arbitrary code into strings, though I couldn't say exactly why. It's definitely not something I've seen in any of the other derive macros I've worked with, so perhaps there is a good reason not to? I spent some time digging through serde issues to see if I could find any discussion of similar things, but alas no.

Two specific concerns I'd have would be:

  • Having to specify self.foo seems like a bit of a maintenance nuisance as it could easily drift from the field name.
  • A path-based annotation forces factoring into functions/newtypes, rather than allowing for anti-patterns like complex expressions in the annotation.

I did come across serde_with which (confusingly, given the name) also orients around an as annotation. In that case though it's referring to a type which the field's type must implement a conversion trait for, so perhaps not a great match for comparison.

I personally still favour an annotation that takes a path to an fn(&T) -> impl Debug, but yeah I can't say that the value = "<expr>" wouldn't work... I'm just a bit uneasy about it 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants