Macro calls in trait position?

I learned today I can't do this:

macro_rules! foo {
    () => {Default + Any};
}

fn bar<T: foo!()>() -> () {
    // ...
}

Is there any good reason to not allow macros in the <T: foo!()> position?

Could you explain why you want to do this and what it permits that isn't currently supported some other way?

I'll describe what my specific use case was but I imagine other people would run into what I think is the more general problem: I was unable to find a way in general to write a macro_rules macro that would let the user pass a list of traits to the macro where you could then actually use those traits.

I missed having type lists from C++. In a program I'm working on I have a Rule trait, and each rule of the business rule of the software is implemented as a separate struct that impls Rule. Rule structs can have multiple "parent" rules, but these parents are specified as types not data. If A is a parent of B, this is true for all instances (and in fact there is only ever one instance of each type). It's kind of like an inheritance tree, although the actual API and semantics I implement are very different from typical OOP inheritance and involve boring specifics of the business domain.

When a Rule type is registered with my RuleProcessor, I also want it to register all of its parent types. In essence there are a list of types I want to iterate over, the parents. So the first version I cooked up looks like:

trait RuleTypeVisitor {
    fn visit<T: Rule + Registrable>(&mut self) -> ();
}

macro_rules! define_rule {
    (name $name:ident, parents { $($parents:ty)* }, condition { $($condition:stmt)* }, body { $($body:stmt)* }) => {
        #[derive(Default, Debug)]
        struct $name(); // omitting fields for brevity

        impl Registrable for $name {
            fn visit_parent_types(visitor: &mut impl RuleTypeVisitor) -> () {
                $(visitor.visit::<$parents>();)*
            }
        }

        // ...omitted for brevity
    }
}

Rules are generated by calling the define_rule! macro, which you pass parent types into. Then it generates a function that can call register on a passed in visitor object. In Rust the visitor can't do anything useful unless the T generic argument to visit has trait bounds, since it's not duck typed like C++ templates.

This isn't very general though, so I thought maybe I should just try and implement a more general type list mechanism. I looked around and I saw people doing more crazy things with type level cons cells and I wondered if my approach would be simpler at least for my use case. So I tried to abstract the idea, starting with just trying to generate the visitor trait:

macro_rules! make_type_list {
    (name $name:ident; traits $($traits:type),*; types $($types:ty),*) => {
        trait TypeVisitor {
            fn visit<T: $($traits)*>(&mut self) -> ();
        }
    };
}

make_type_list!(name foo; traits Default, Any; types i32, i64);

The idea being each type list would generate its own specific-to-visiting-that-list visitor trait that could assume whatever traits the user specified as being common to all types in the list (I would have to do something about making the trait name unique). But this doesn't work because Traits are separated by +, not commas. So then I tried:

macro_rules! make_type_list {
    (name $name:ident; traits $($traits:ty),*; types $($types:ty),*) => {
        trait TypeVisitor {
            fn visit<T: $($traits)+*>(&mut self) -> ();
        }
    };
}

make_type_list!(name foo; traits Default, Any; types i32, i64);

But this won't work because + refers to wanting to expand one or more, not separating by +. So then I tried:

macro_rules! plus_paste {
    () => {};
    ($head:tt) => { $head };
    ($head:tt, $($tail:tt),+) => {$head + plus_paste!($($tail)*)};
}

macro_rules! make_type_list {
    (name $name:ident; traits $($traits:ty),*; types $($types:ty),*) => {
        trait TypeVisitor {
            fn visit<T: plus_paste!($($traits),*)>(&mut self) -> ();
        }
    };
}

make_type_list!(name foo; traits Default, Any; types i32, i64);

Which is when I learned what started this thread. I also tried taking the list as a token tree so the user would write:

macro_rules! make_type_list {
    (name $name:ident; traits $traits:tt; types $($types:ty),*) => {
        trait TypeVisitor {
            fn visit<T: $traits>(&mut self) -> ();
        }
    };
}

make_type_list!(name foo; traits (Default + Any); types i32, i64);

But this errors as well because of the +. Expression doesn't work either.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.