3

I have around 10 structs with between 5-10 fields each and I want to be able to print them out using the same format.

Most of my structs look like this:

struct Example {
  a: Option<String>,
  b: Option<i64>,
  c: Option<String>,
  ... etc
}

I would like to be able to define a impl for fmt::Display without having to enumerate the fields again so there is no chance for missing one if a new one is added.

For the struct:

let eg = Example{
  a: Some("test".to_string),
  b: Some(123),
  c: None,
}

I would like the output format:

a: test
b: 123
c: -

I currently am using #[derive(Debug)] but I don't like that it prints out Some(X) and None and a few other things.

If I know that all the values inside my structs are Option<T: fmt::Display> can I generate myself a Display method without having to list the fields again?

Nick
  • 900
  • 1
  • 10
  • 19
  • Possible duplicate of [Using reflection to enumerate through the fields of a struct at runtime](https://stackoverflow.com/questions/30407009/using-reflection-to-enumerate-through-the-fields-of-a-struct-at-runtime) – trent Oct 23 '19 at 12:52
  • Or perhaps not... you did not specify it has to be done at runtime. Certainly this can be done at compile time with a proc macro, although I don't know how myself. – trent Oct 23 '19 at 12:54
  • I would rather have this done at compile time, but runtime could work too if necessary. I did consider macros but I am not sure how to write one that would do what I want. – Nick Oct 23 '19 at 14:16

2 Answers2

6

This may not be the most minimal implementation, but you can derive serialisable and use the serde crate. Here's an example of a custom serialiser: https://serde.rs/impl-serializer.html

In your case it may be much simpler (you need only a handful of types and can panic/ignore on anything unexpected).

Another approach could be to write a macro and create your own lightweight serialisation solution.

viraptor
  • 33,322
  • 10
  • 107
  • 191
  • Thanks for the suggestion, this was useful for a different problem I had later! I decided to go with my macro solution below which is heavily based on: https://stackoverflow.com/a/54177889/1355121 – Nick Oct 23 '19 at 20:28
4

I ended up solving this with a macro. While it is not ideal it does the job.

My macro currently looks like this:

macro_rules! MyDisplay {
    ($struct:ident {$( $field:ident:$type:ty ),*,}) => {
        #[derive(Debug)]
        pub struct $struct { pub $($field: $type),*}

        impl fmt::Display for $struct {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                $(
                    write!(f, "{}: {}\n",
                        stringify!($field).to_string(),
                        match &self.$field {
                            None => "-".to_string(),
                            Some(x) => format!("{:#?}", x)
                        }
                    )?;
                )*
                Ok(())
            }
        }
    };
}

Which can be used like this:

MyDisplay! {
    Example {
        a: Option<String>,
        b: Option<i64>,
        c: Option<String>,
    } 
}

Playground with an example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cc089f8aecaa04ce86f3f9e0307f8785

My macro is based on the one here https://stackoverflow.com/a/54177889/1355121 provided by Cerberus

Nick
  • 900
  • 1
  • 10
  • 19