Макрос, который динамически генерирует средства форматирования в Rust

Я пишу макрос для динамического создания средств форматирования, таких как Display и Debug, для заданной структуры, содержащей один универсальный тип. Код следующий:

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)
            }
        }
    };
}

Макрос вызывается create_formatters!(MyStruct<T>, std::fmt::Display); или create_formatters!(MyStruct<T>, std::fmt::Debug);

Компилятор выдает следующую ошибку:

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`

Как я могу это исправить?


person Benedikt Schumacher    schedule 08.01.2017    source источник


Ответы (1)


Почему вы получаете эту ошибку? Давайте посмотрим на расширение 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)
    }
}

Здесь T ограничено только Display, но где-то внутри импл-тела вы используете средство форматирования {:?} с типом T. Да, случай совпадения с {:?} никогда не будет выполняться во время выполнения, но компилятор не может этого знать в общем случае. Код для каждого спичечного рычага еще нужно сгенерировать! А сделать это, очевидно, невозможно.

Как это исправить?

Вероятно, самое чистое решение — полностью отказаться от использования строк форматирования. Если у вас есть переменная типа T, которая реализует трейт, вы можете просто вызвать метод трейта напрямую:

fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    self.0.fmt(f)
}
person Lukas Kalbertodt    schedule 08.01.2017