Создание перечисления внутри макроса
Можно ли создать перечисление внутри макроса Rust, используя поля, которые определены как параметры макроса? Я пробовал это:
macro_rules! build {
($($case:ty),*) => { enum Test { $($case),* } };
}
fn main() {
build!{ Foo(i32), Bar(i32, i32) };
}
Но это не с error: expected ident, found 'Foo(i32)'
Обратите внимание, что если поля определены внутри перечисления, проблем нет:
macro_rules! build {
($($case:ty),*) => { enum Test { Foo(i32), Bar(i32, i32) } };
}
fn main() {
build!{ Foo(i32), Bar(i32, i32) };
}
Это также работает, если мой макрос принимает только простые поля:
macro_rules! build {
($($case:ident),*) => { enum Test { $($case),* } };
}
fn main() {
build!{ Foo, Bar };
}
Но я не смог заставить его работать в общем случае.
1 ответ
Это абсолютно возможно, но вы объединяете совершенно не связанные понятия.
Что-то вроде $case:ty
не значит $case
это то, что выглядит как тип, это означает, $case
это буквально тип. Перечисления не состоят из последовательности типов; они состоят из последовательности вариантов, которые представляют собой идентификатор, за которым (необязательно) следует тело структуры кортежа, тело структуры записи или значение тега.
Синтаксическому анализатору все равно, если тип, который вы передаете ему, по совпадению выглядит как допустимый вариант, он просто не ожидает тип и откажется анализировать один в этой позиции.
Что вам нужно, это использовать что-то вроде $case:variant
, К сожалению для вас, такого соответствия не существует. Единственный способ сделать что-то подобное - это вручную проанализировать это с помощью рекурсивного инкрементального синтаксического анализатора, и это так выходит за рамки вопроса SO, это не смешно. Если вы хотите узнать больше, попробуйте в качестве отправной точки главу об инкрементных молоточках TT в "Маленькой книге макросов ржавчины".
Тем не менее, вы, кажется, на самом деле ничего не делаете с делами. Вы просто слепо подставляете их. В этом случае вы можете просто обмануть и не пытаться сопоставить что-либо связное:
macro_rules! build {
($($body:tt)*) => {
as_item! {
enum Test { $($body)* }
}
};
}
macro_rules! as_item {
($i:item) => { $i };
}
fn main() {
build!{ Foo, Bar };
}
(Кстати, это as_item!
это объясняется в разделе о принуждении к AST (так называемый "трюк с повторным использованием").)
Это просто захватывает все, что предоставляется в качестве входных данных для build!
и впихивает его в тело enum
не заботясь о том, как это выглядит.
Если вы пытались сделать что-то осмысленное с вариантами, то вам нужно будет более конкретно рассказать о том, что вы на самом деле пытаетесь достичь, так как лучший совет о том, как действовать, сильно варьируется в зависимости от ответа.