Macro_rules, dealing with bound with multiple constraints

I am trying to define a macro_rules macro that will generate struct definitions, which may or may not be generic, and where the types it is generic on may or may not have multiple bounds. I don't need lifetime bounds to work, only trait bounds, but it's still proving pretty tricky. I'm having trouble getting even one generic argument to work, because I can't seem to generate PhantomData right. Any clues what I'm doing wrong here?

Playground link

macro_rules! foo {
    (name($name:ident$(<$($generic_type_name:ident $(: $($generic_type_bound:path),+)?),+>)?)) => {
        struct $name $(<$($generic_type_name $(: $($generic_type_bound),+)?),+>)? {
            test: i32,
            // this doesn't seem to work? seems to want to require me to also expand generic_type_bound?
            //_phantom: std::marker::PhantomData<($(<$($generic_type_name),+>)?)>
        }
    }
}

// no generics should work, and does!
foo!(name(bar));

// generics with a type with no bounds should work, doesn't b/c PhantomData expands wrong?
//foo!(name(bar2<T>));

// generics with a type with a bound should work, doesn't b/c PhantomData expands wrong?
//foo!(name(bar3<T: Clone>));

// generics with a type with multiple bounds should work, doesn't b/c + is unexpected
//foo!(name(bar4<T: Clone + EQ>));

You have an extra set of angle brackets in phantom data (which I think the compiler is then expecting turbofish syntax). Also you want to make _phantom conditional on the generic types being present. I removed the type bounds in the playground I was playing with to simplify the problem, but I assume they'd be easy to add back in. This seems to work in my simplified version without the type bounds

macro_rules! foo {
    (name($name:ident$(<$($generic_type_name:ident),+>)?)) => {
        struct $name$(<$($generic_type_name),+>)? {
            test: i32,
            $(
                _phantom: std::marker::PhantomData<$($generic_type_name),+>
            )?
        }
    }
}

Edit:
This works with type bounds and fixes an issue with multiple generics (I needed to add parenthesis around the generics in phantom data to make it a tuple since PhantomData only takes one generic

macro_rules! foo {
    (name($name:ident$(<$($generic_type_name:ident $(: $($generic_type_bound:path),+)?),+>)?)) => {
        struct $name$(<$($generic_type_name $(: $($generic_type_bound),+)?),+>)? {
            test: i32,
            $(
                _phantom: std::marker::PhantomData<($($generic_type_name),+)>
            )?
        }
    }
}

Great, thanks, that fixes the PhantomData problem. I still can't seem to get multiple generic arguments with a single bound ("local ambiguity") or a single generic argument with multiple bounds ("unexpected +") to work though.

Updated playground link

#![allow(non_camel_case_types)]
#![allow(dead_code)]

macro_rules! foo {
    (name($name:ident$(<$($generic_type_name:ident $(: $($generic_type_bound:path),+)?),+>)?)) => {
        struct $name $(<$($generic_type_name $(: $($generic_type_bound),+)?),+>)? {
            test: i32,
            $(_phantom: std::marker::PhantomData<($($generic_type_name),+,)>)?
        }
    }
}

// no generics should work, and does!
foo!(name(bar));

// generics with a type with no bounds should work
foo!(name(bar2<T>));

// multiple generic arguments should work
foo!(name(bar3<T, S>));

// generics with multiple arguments with bounds should work, doesn't work b/c "local ambiguity"
//foo!(name(bar5<T: Clone, S: Eq>));

// generics with a type with multiple bounds should work, doesn't b/c + is unexpected
//foo!(name(bar6<T: Clone + EQ>));

Turns out type bounds are tricky :slight_smile:, but got something working.

This works with type bounds and multiple generics. (I'm not sure whether ident is right for the generics and type bounds). I also indented all the parenthesis to make it easier to see how things match up, which proved to be much easier than trying to do it all in one line.

playground

macro_rules! foo {
    (name
        ($name:ident
            $(<
                $(
                    $generic_type_name:ident
                    $(
                        : $generic_type_bound:ident
                        $(
                            + $generic_type_bound2:ident
                        )*
                    )?
                ),+
            >)?
        )
    )=> {
        struct $name$(
            <$(
                $generic_type_name
                $(
                    : $generic_type_bound
                    $(
                        + $generic_type_bound2:ident
                    )*
                )?
            ),+
        >)? {
            test: i32,
            $(
                _phantom: std::marker::PhantomData<($($generic_type_name),+)>
            )?
        }
    }
}

Ah that's clever for working around + not working as a separator.

Unfortunately it looks like using ident breaks using scoping to refer to traits. I tried using path instead, but it complains path is not allowed to be followed by + (no idea why), same issue with using ty. See new usage example at the bottom.

New playground link

#![allow(non_camel_case_types)]
#![allow(dead_code)]
#![allow(unused_macros)]

macro_rules! foo {
    (name($name:ident$(<$($generic_type_name:ident $(: $generic_type_bound1:path $(+ $generic_type_bound2:path)*)?),+>)?)) => {
        struct $name $(<$($generic_type_name $(: $generic_type_bound1 $(+ $generic_type_bound2)*)?),+>)? {
            test: i32,
            $(_phantom: std::marker::PhantomData<($($generic_type_name),+,)>)?
        }
    }
}

// no generics should work, and does!
foo!(name(bar));

// generics with a type with no bounds should workfoo!
foo!(name(bar2<T>));

// multiple generic arguments should work
foo!(name(bar3<T, S>));

// generics with multiple arguments with bounds should work
foo!(name(bar5<T: Clone, S: Eq>));

// generics with a type with multiple bounds should work
foo!(name(bar7<T: Clone + Eq>));

// should work even if trait is inside a module, but unexpected token '::'
mod inner {
    trait MyTrait {}
}
//foo!(name(bar7<T: inner::MyTrait>));

Hmm, that seems to be impossible with macro_rules. I can't find a work around. My guess is you'll need to make a proc macro.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.