Как создать параметризованный тип из макроса?

У меня есть макрос, который создает структуру и кучу поддерживающих функций и реализаций признаков. Интересный бит для этого вопроса:

macro_rules! make_struct {
    ($name: ident) => {
        struct $name;
    }
}

Это работает так, как вы ожидаете:

make_struct!(MyStruct);

Если я хочу сделать параметризованный тип, мне не повезло:

make_struct!(AnotherStruct<T: SomeTrait>);

test.rs:8:27: 8:28 error: no rules expected the token `<`
test.rs:8 make_struct!(AnotherStruct<T: SomeTrait>);

Имя структуры является ident, поэтому я не могу просто изменить это в макросах args (например, до ty):

test.rs:3:16: 3:21 error: expected ident, found `MyStruct`
test.rs:3         struct $name;

Итак, как я могу написать этот макрос, чтобы иметь возможность обрабатывать оба? Или мне нужно разделить их? В последнем случае, как выглядит макрос?

Ответ 1

После ключевого слова struct парсер ожидает дерево токенов идентификатора, за которым может следовать < и больше; ty определенно не то, что он хочет (в качестве примера того, почему он не работает, (Trait + Send + 'static) является допустимым типом, но struct (Trait + Send + 'static); явно не имеет смысла).

Для поддержки дженериков вам нужно будет создать больше правил.

macro_rules! make_struct {
    ($name:ident) => {
        struct $name;
    };
    ($name:ident<$($t:ident: $constraint:ident),+>) => {
        struct $name<$($t: $constraint),+>;
    }
}

Как вы, несомненно, заметите, что он поддерживает все, что парсер будет принимать в этом месте, почти невозможно; macro_rules is not that clever. Тем не менее, этот один странный трюк (анализаторы статического кода ненавидят!), Который позволяет вам взять последовательность деревьев токенов и рассматривать его как обычное определение struct. Он использует немного больше косвенных ссылок с macro_rules:

macro_rules! item {
    ($item:item) => ($item);
}
macro_rules! make_struct {
    ($name:ident) => {
        struct $name;
    };
    ($name:ident<$($tt:tt)*) => {
        item!(struct $name<$($tt)*;);
    };
}

Обратите внимание, что из-за чрезмерности, ident в настоящее время не допускает повторение последовательности ($(…)), чтобы немедленно следовать за ней, поэтому вы будете заклеивать, помещая некоторое дерево токенов между ident и повторением, например. $name:ident, $($tt:tt)* (давая AnotherStruct, <T: SomeTrait>) или $name:ident<$(tt:tt)* = > struct $name<$($tt)*;. Ценность этого сводится к тому, что вы не можете полностью разделить его на отдельные типы, которые вам нужно будет сделать для таких вещей, как вставка маркеров PhantomData.

Вам может быть полезно передать весь элемент struct; он проходит как тип item (так же, как a fn, enum, use, trait, & c.).