简体   繁体   中英

Macro that generates formatters dynamically in Rust

I'm writing a macro to dynamically generate formatters like Display and Debug for a given struct that contains a single generic type. The code is the following:

macro_rules! create_formatters {
    ($type_name:ident < $gen_param:ident > ,  $t:path) => {
        impl<$gen_param: $t> $t for $type_name<$gen_param> {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
                let output = match stringify!($t) {
                    "std::fmt::Display" => format!("{}", self.0),
                    "std::fmt::Debug" => format!("{:?}", self.0),
                    // other formatters will be implemented soon
                };
                write!(f, "Content is: {}", output)
            }
        }
    };
}

The macro is called by create_formatters!(MyStruct<T>, std::fmt::Display); or create_formatters!(MyStruct<T>, std::fmt::Debug);

The compiler gives the following error:

error[E0277]: the trait bound `T: std::fmt::Debug` is not satisfied
  --> <anon>:8:58
   |
8  |                     "std::fmt::Debug" => format!("{:?}", self.0),
   |                                                          ^^^^^^ the trait `std::fmt::Debug` is not implemented for `T`
...
28 | create_formatters!(Swagger<T>, std::fmt::Display);
   | -------------------------------------------------- in this macro invocation
   |
   = help: consider adding a `where T: std::fmt::Debug` bound
   = note: required by `std::fmt::Debug::fmt`

How can I fix it?

Why are you getting this error? Let's take a look at the expansion of create_formatters!(MyStruct<T>, std::fmt::Display); :

impl<T: std::fmt::Display> std::fmt::Display for MyStruct<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        let output = match "std::fmt::Display" {
            "std::fmt::Display" => format!("{}", self.0),
            "std::fmt::Debug" => format!("{:?}", self.0),
            // other formatters will be implemented soon
        };
        write!(f, "Content is: {}", output)
    }
}

Here, T is only bounded to be Display , but somewhere inside the impl-body, you use the {:?} formatter with the type T . Yes, the match case with {:?} will never be executed during runtime, but the compiler can't know that in the general case. The code for every match arm still needs to be generated! And this is obviously impossible to do.

How to fix it?

Probably the cleanest solution is to avoid the use of formatter strings entirely. If you have a variable of type T which implements a trait, you can just call the method of the trait directly:

fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    self.0.fmt(f)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM