Рекурсивный макрос Rust не работает для генерации структуры

#rust #macros #rules

#Ржавчина #макросы #Правила

Вопрос:

Я пытаюсь написать макрос, который генерирует структуру в Rust. Этот макрос добавит различные атрибуты Serde к полям структуры на основе типа поля. Это конечная цель.

На данный момент я просто пытаюсь написать макрос, который использует другой макрос для генерации рекурсивного кода.

Вот как выглядит код:

 macro_rules! f_list {
    ($fname: ident, $ftype: ty) => {
        pub $fname: $ftype,
    }
}

macro_rules! mk_str {
    ($sname: ident; $($fname: ident: $ftype: ty,) ) => {
        #[derive(Debug, Clone)]
        pub struct $sname {
            $(
                f_list!($fname, $ftype)
            ) 
        }
    }
}

mk_str! {
    Yo;
    name: String,
}

fn main() {
    println!("{:?}", Yo { name: "yo".to_string() })
}
  

Этот код при запуске выдает приведенную ниже ошибку, которую я не могу понять.

 error: expected `:`, found `!`
  --> src/main.rs:12:23
   |
12 |                   f_list!($fname, $ftype);
   |                         ^ expected `:`
  

Что здесь не так?

Вот ссылка на игровую площадку

Ответ №1:

При написании декларативных макросов ( macro_rules! ) важно понимать, что результатом макроса должен быть шаблон, инструкция, выражение, элемент или impl . По сути, вы должны думать о выходе как о чем-то, что может быть автономным, с синтаксической точки зрения.

В вашем коде макрос f_list , если бы он работал, выводил бы код типа

 name1: type1,
name2: type2,
name3: type3,
  

Хотя это может быть частью объявления для структуры, само по себе это не является чем-то самостоятельным.

Тогда почему ошибка в другом макросе? mk_str успешно расширяется до

 #[derive(Debug, Clone)]
pub struct Yo {
    f_list!(name, String)
}
  

Однако анализатор не ожидает макрос внутри объявления структуры. Внутренняя часть структуры — это не шаблон, оператор, выражение, элемент или impl . Таким образом, когда он видит ! , он сдается и сообщает об ошибке.

Как вы можете это исправить? В этом конкретном примере f_list является довольно избыточным. Вы могли бы просто заменить f_list!($fname, $ftype) на pub $fname: $ftype, in mk_str , и это будет работать так, как написано. Если это не подходит для вашей цели, взгляните на The Little Book of Rust Macros. В нем есть несколько шаблонов для выполнения очень сложных действий с макросами. Большая часть информации в этом ответе взята из раздела «Макросы в AST».

Комментарии:

1. Спасибо! Это устраняет мои сомнения. Идея с f_list заключалась в том, чтобы иметь что-то вроде этого: macro_rules! f_list { ($fname: ident, Vec<$ftype: ty>) => { #[serde(по умолчанию)] pub $fname: Vec<$ftype>, } ($fname: ident, $ftype: ty) => { pub $fname: $ ftype, } }