Feature Request: Anonymous Macro Expansion

It's very often that I find myself writing and reading code like this:

trait Thing {/*...*/}

macro_rules! impl_thing {
  ($($Ts: ty),*) => {
    impl Thing for $T { /*...*/ }
  }
}
impl_thing!(Type1, Type2, Type3 /*...*.)

It would be nice to avoid having to create an intermediary macro. An example syntax:

expand (Type1, Type2, Type3) in ($($Ts: ty),*) {
  impl Thing for $T { /*...*/ }
}

This would simply desugar to the macro above and its subsequent invocation, minus having a reusable name. In general:

expand (<tokens>) in (<pattern>) { <body> }

would become:

macro_rules! <anonymized name> {
  (<pattern>) => { <body> }
}
<anonymized name>!(<tokens>)

(Or its equivalent Macros 2.0 syntax. expand_rules! could be used to match macro_rules! more closely and also allow a transition period to a decl_macro-accompanying expand) Multiple patterns and recursion could also still be accomplished with some more syntax. Example:

expand <macro-local name>(<tokens>) {
  (<pattern1>) => { <body1> };
  (<pattern2>) => { <body2> }
}

I am not particularly worried about the specific syntax, of course, as long as there is a solution to stop avoiding writing one-time use intermediary macros. It would make the code more succinct and clear.

3 Likes

Would a macro like this work?

macro_rules! expand {
    (($($inputs:tt)*) in ($($pattern:tt)*) { $($body:tt)* }) => {
        macro_rules! inner {
            ($($pattern)*) => { $($body)* }
        }
        inner!($($inputs)*)
    }
}

Which you could then use as:

expand!((Type1, Type2, Type3) in ($($T:ty),*) {
    $(
        impl Thing for $T { /* ... */ }
    )
})
4 Likes

Certainly a crate with a macro like this would be a good prototype at least, if not a solution. I guess I'd need to use it for a while to see if it gets the job done. It can probably be expanded to accomplish multiple patterns, altho recursion would require a proc macro with a explicit "def_site()" Span. (altho if we're only talking one-level-deep recursion then naming the macro as recurse or something, it could be treated as a custom keyword. There's a feature like this in clojure where loops are done via recursion with a recur command)

1 Like

So far I've found a couple limitations with the macro implementation. It cannot be used inside trait or type definitions, impls, and it cannot expand into a type. This is because none of those contexts accept a macro definition, it must be done outside. Ideally expand would be treated as a singular macro-call that could be used anywhere a macro could.

struct X;
impl X {
  // will cause an error
  expand! {
    (x,y,z) in ($($f:ident)) {
      fn $f() -> usize { 10 }
    }
  }
}

I think there are also a few crates that provide macros similar to this, though I don't have a specific one to recommend.

1 Like

So far I've found a couple limitations with the macro implementation. It cannot be used inside trait or type definitions, impls, and it cannot expand into a type.

With regards to call site limitations, this can be dealt with by creating an attribute macro.

#[expand_inline]
impl X {
  expand!(...) // syn macro call item
}

#[expand_inline]
type expand!(...) = expand!(...);

It would probably require some special handling in the second case as syn might not recognize the second expression as a type declaration, but these edge cases could be handled with a wrapper parser like:

pub enum Expandable<T> {
  Expand(ExpandMacro),
  Other(T)
}

impl Parser for Expandable<T> {
  // test if expand!, if not try T, else error as "not T"
}

and then I guess some expressions for those edge cases would have to be copied from syn and augmented with Expandable<T>. The only issue with that approach is having to play catch-up with syn/Rust syntax.

I really like this idea btw. I would definitely use it some times (probably a lot).

1 Like