Help w/ a macro rules

This is inspired by @vague 's surprising answer at Listing all branches of enums w/ 'unit' arms - #2 by vague

Now I am wondering if the following (which I previously considered impossible) to be possible with macro rules:

We start with the end goal (manually written)

use super::*;

pub struct Bar {}

pub trait Bar_Aux_T {
    fn cat(&self, obj: &Bar, x: u32);

    fn dog(&self, obj: Rc<Bar>, y: String);

    fn apple(&self, obj: &Bar, a: i32, b: i32);
}

impl Bar {
    fn cat(self: &Bar, aux: &dyn Bar_Aux_T, x: u32) {
        aux.cat(self, x)
    }

    fn dog(self: Rc<Bar>, aux: &dyn Bar_Aux_T, y: String) {
        aux.dog(self, y)
    }

    fn apple(self: &Bar, aux: &dyn Bar_Aux_T, a: i32, b: i32) {
        aux.apple(self, a, b)
    }
}

Now, the question is: can we wrap a macro around pub trait Bar_T and have it auto generate the part in impl Bar as follows:

  • the only thing in the trait are function declarations

  • there are only two types of function declarations

  1. fn func_name(&self, obj: &Bar, ...)
  2. fn func_name(&self, obj: Rc<Bar>, ...)
  • these need to, respectively transformed into:
  1. fn func_name(&self, obj: &dyn Bar_Aux_T, ...)
  2. fn func_name(self: Rc<Self>, obj: &dyn Bar_Aux_T, ...)

===

Why can't I do this myself? If we look at @vague 's previous solution there is "repetition" and "binding" but there isn't what I'd call "enum choice"

So in the parsing part, we need to somehow store whether we are parsing a obj: &Bar or an obj: Rc<Bar> , then later we need to "dispatch" and do different things based on which one we got.

This is the part I can not figure out.

Concretely, I need help writing a macro_rules foo s.t.

foo! {
pub trait Bar_Aux_T {
    fn cat(&self, obj: &Bar, x: u32);

    fn dog(&self, obj: Rc<Bar>, y: String);

    fn apple(&self, obj: &Bar, a: i32, b: i32);
}
}

will auto generate the

impl Bar {
    fn cat(self: &Bar, aux: &dyn Bar_Aux_T, x: u32) {
        aux.cat(self, x)
    }

    fn dog(self: Rc<Bar>, aux: &dyn Bar_Aux_T, y: String) {
        aux.dog(self, y)
    }

    fn apple(self: &Bar, aux: &dyn Bar_Aux_T, a: i32, b: i32) {
        aux.apple(self, a, b)
    }
}

Thanks!

use std::rc::Rc;

macro_rules! impl_aux {
    (
        $vis:vis trait $tr_name:ident for $ty_name:ident {
            $($items:tt)*
        }
    ) => {
        $vis trait $tr_name {
            $($items)*
        }

        impl_aux! {
            {
                $tr_name,
                [ impl $ty_name ],
                []
            }
            
            $($items)*
        }
    };
    
    (
        {
            $_tr_name:ident,
            [ $($header:tt)* ],
            [ $($body:tt)* ]
        }
    ) => {
        $($header)* { $($body)* }
    };

    (
        {
            $tr_name:ident,
            $header:tt,
            [$($out:tt)*]
        }

        fn $fn_name:ident(&self, $_obj:ident: $obj_ty:ty $(, $arg_name:ident: $arg_ty:ty)*) $( -> $r_ty:ty)?;
        $($tail:tt)*
    ) => {
        impl_aux! {
            {
                $tr_name,
                $header,
                [
                    $($out)*

                    fn $fn_name(self: $obj_ty, aux: &dyn $tr_name $(, $arg_name: $arg_ty)*) $(-> $r_ty)? {
                        aux.$fn_name(self $(, $arg_name)*)
                    }
                ]
            }
            
            $($tail)*
        }
    };
}

pub struct Bar {}

impl_aux! {
    pub trait Bar_Aux_T for Bar {
        fn cat(&self, obj: &Bar, x: u32);
    
        fn dog(&self, obj: Rc<Bar>, y: String);
    
        fn apple(&self, obj: &Bar, a: i32, b: i32);
    }
}
2 Likes

This is beautifully formatted.

Are you:

(1) distinguishing the obj: &Bar vs obj: Rc<Bar> case OR

(2) utilizing the fact that

impl Bar {

  fn a(&self, ...)
  fn b(self: Rc<Self>, ...)
}

is equiv to

impl Bar {
  fn a(self: &Bar, ...)
  fn b(self: Rc<Bar>, ...)
}
``

and thus no need to distinguish the two cases, only to parse the function args ?

I am reasonably sure the second is true (i.e. &self is just shorthand for self: &Self). If not, it would require an additional level of indirection and some extra rules.

Edit: Also, normally the intermediate rules would have prefix names like @parse_fn, but I just woke up and forgot. So it's not as nice as it could be.

The code, as is, is great. Until now, I was not even possible it was able to PARSE Rust declarations like this and rewrite them. I'm merely still adjusting to reading macro_rules . It's breaking my intuition of marco_rules == merely cfg.

It is.

you can't "store" values in macro expansion, you match with different arm and dispatch accordingly.

this is doable, though not very robust, it's hard to cover edge cases. e.g.

1 Like

Thanks. I''m not comfortable with macro_rules yet, but the following snippet was what I was hoping for when I formulated the question:

    (@expand_impl $trait_name:ident $method_name:ident Rc<$obj_type:ty>, $($arg:ident : $arg_type:ty),*) => {
        impl $obj_type {
            fn $method_name(self: Rc<Self>, aux: &dyn $trait_name, $($arg: $arg_type),*) {
                aux.$method_name(self, $($arg),*)
            }
        }
    };
    // matches &Bar
    (@expand_impl $trait_name:ident $method_name:ident & $obj_type:ty, $($arg:ident : $arg_type:ty),*) => {
        impl $obj_type {
            fn $method_name(&self, aux: &dyn $trait_name, $($arg: $arg_type),*) {
                aux.$method_name(self, $($arg),*)
            }
        }
    }

I guess this is what you also mean by

you can't "store" values in macro expansion, you match with different arm and dispatch accordingly.

we're not parsing + storing; we are pattern matching + immediately deciding the output.

I am trying to understand why, at:

    {
        $vis:vis trait $trait_name:ident {
            // we cannot use `ty` to capture $obj_type,
            // otherwise we cannot match it again and dispatch in next step
            // we don't have alternative combinator, so we use two optional combinators
            $(fn $method_name:ident(&self, obj: $(Rc<$obj_type_rc:ty>)? $(& $obj_type_ref:ty )?  $(, $arg:ident : $arg_type:ty)* $(,)?);)+
        }

    } => {
        $vis trait $trait_name {
            $(fn $method_name(&self, obj:  $(Rc<$obj_type_rc>)? $(& $obj_type_ref)?, $($arg : $arg_type),*);)+
        }

        $(foo!{@expand_impl $trait_name $method_name  $(Rc<$obj_type_rc>)? $(& $obj_type_ref)?, $($arg : $arg_type),*})+

    };

We do not do a

impl $obj_type { 
        $(foo!{@expand_impl $trait_name $method_name  $(Rc<$obj_type_rc>)? $(& $obj_type_ref)?, $($arg : $arg_type),*})+
}

instead of having an impl $obj_type at every function expansion.

Is the reason for this that there is no clean/easy way to get $obj_type at the earlier point? I.e. until we see Rc<Bar> or an &Bar, we don't know that the obj type has name Bar ?

short description

Last episode:

obj: &Bar,
obj: Rc<Bar>

I now want to extend this to also handle

obj: ()

which would then define a static function.

problem statement

pub trait Bar_Aux_T {
    fn cat(&self, obj: (), x: u32); // NEW

    fn dog(&self, obj: Rc<Bar>, y: String);

    fn apple(&self, obj: &Bar, a: i32, b: i32);
}

impl Bar {
    fn cat(aux: &dyn Bar_Aux_T, x: u32)  {  // NEW
        aux.cat(self, x)
    }

    fn dog(self: Rc<Bar>, aux: &dyn Bar_Aux_T, y: String) {
        aux.dog(self, y)
    }

    fn apple(self: &Bar, aux: &dyn Bar_Aux_T, a: i32, b: i32) {
        aux.apple(self, a, b)
    }
}

My attempt so far:

#[macro_export]
macro_rules! foo {
    {
        $vis:vis trait $trait_name:ident {
            $(fn $method_name:ident(&self, obj: $(())? $(Rc<$obj_type_rc:ty>)? $(& $obj_type_ref:ty)?  $(, $arg:ident : $arg_type:ty)* $(,)?)
            $( -> $r_ty:ty)?
            ;)+
        }
    } => {
        $vis trait $trait_name {
            $(fn $method_name(&self, obj: $(())? $(Rc<$obj_type_rc>)? $(& $obj_type_ref)? $(())?, $($arg : $arg_type), *) $( -> $r_ty:ty)? ;)+
        }

        $(foo! { @expand_impl $(&self,)? $trait_name $method_name $(())? $(Rc<$obj_type_rc>)? $(& $obj_type_ref)? , $($arg: $arg_type), *,  $( -> $r_ty:ty)? })+
    };

    (@expand_impl $trait_name:ident $method_name:ident Rc<$obj_type:ty>, $($arg:ident : $arg_type:ty), *,
    $( -> $r_ty:ty)?
    ) => {
        impl $obj_type {
            fn $method_name(self: Rc<Self>, aux: &dyn $trait_name, $($arg: $arg_type), *) $( -> $r_ty:ty)? {
                aux.$method_name(self, $($arg), *)
            }
        }
    };

    (@expand_impl $trait_name:ident $method_name:ident (), $($arg:ident : $arg_type:ty), *, $( -> $r_ty:ty)?
    ) => {
        impl $obj_type {
            fn $method_name( aux: &dyn $trait_name, $($arg: $arg_type), *) $( -> $r_ty:ty)? {
                aux.$method_name((), $($arg), *)
            }
        }
    };

    (@expand_impl &self, $trait_name:ident $method_name:ident & $obj_type:ty, $($arg:ident : $arg_type:ty), *, $( -> $r_ty:ty)? ) => {
        impl $obj_type {
            fn $method_name(&self, aux: &dyn $trait_name, $($arg: $arg_type), *) $( -> $r_ty:ty)? {
                aux.$method_name(self, $($arg), *)
            }
        }
    }
}

I am getting an error about

   |
45 | ...   $(fn $method_name(&self, obj: $(())? $(Rc<$obj_type_rc>)? $(& $obj_type_ref)? $(())?, $($arg : $arg_type), *) $( -> $r_ty:ty)? ;)+
   |                                      ^^^^


the "optional" operator (i.e. question mark) needs a captured meta variable inside, otherwise, you cannot expand it later.

at this point, I think you can just ditch the check on the entry point arm, just capture the argument list using tt and forward everything to the expansion arms.

however, because you cannot match and extract Bar from the type () (unlike &Bar or Rc<Bar>), you'll have to either hardcode the Bar type for the impl blocks, or you have to explicit pass the type Bar at the macro "callsite". one example syntax might be:

foo! {
    pub trait Bar_Aux_T {
        //...
    }
    // just for illustration purpose, you can come up with whatever syntax you like
    impl for Bar;
}

full code:

1 Like
  1. Got this working; thanks!

  2. Using () is much nicer than my solution of Nothing<T> via How do I emit a literal '::' in macro_rules?

  3. @nerditation Thank you for taking your time to write multiple iterations of the solution; This is much faster learning than The Little Book of Rust Macros

  4. @DanielKeep : Did not end up going with your code, but plagerized the return type matching / emitting from your example. Thanks!

1 Like