Access to self from the parameters of a macro that

2020-04-17 06:55发布

问题:

I'm trying to write a macro that generates a struct. The implementation for the struct will be generated by the macro, but some blocks of code will be provided as macro arguments. Here is a minimal example of such a macro:

macro_rules! make_struct {
    ($name:ident $block:block) => {
        struct $name {
            foo: i32,
        }

        impl $name {
            fn new() -> Self {
                $name { foo: 42 }
            }
            fn act (&self) {
                $block
            }
        }
    };
}

And here is an example of how the macro can be used:

fn main() {
    // Works
    make_struct!(Test { println! ("Bar: {:?}", 24); });
    let test = Test::new();
    test.act();
}

I would like to give access to self inside the supplied code. Something like:

fn main() {
    // Does not work
    make_struct!(Test { println! ("Foo: {:?}", self.foo); });
    let test = Test::new();
    test.act();
}

I understand that this does not work because of macro hygiene rules. Specifically, the self.foo expression is evaluated in the syntax context of the main function, where self does not exist. The question is: is there a way to modify the macro so that self may be accessed from the user-supplied code?

Code on playground

回答1:

You can pass a closure instead of a block.

make_struct!(Test |this| println!("Foo: {:?}", this.foo));

Then the macro can use the closure and call it with self:

macro_rules! make_struct {
    ($name:ident $closure:expr) => {
        struct $name {
            foo: i32,
        }

        impl $name {
            fn new() -> Self {
                $name { foo: 42 }
            }
            fn act (&self) {
                let x: &Fn(&Self) = &$closure;
                x(self)
            }
        }
    };
}

The dance with the let binding is necessary, because the type of this in the closure can't be inferred (yet?). And it also makes your macro's error reporting a little more readable when something other than a closure is passed.



回答2:

Found a way to do it by adding a parameter to the macro that stores the name by which self will be accessed in the blocks:

macro_rules! make_struct {
    ($myname:ident : $type_name:ident $block:block) => {
        struct $type_name {
            foo: i32,
        }

        impl $type_name {
            fn new() -> Self {
                $type_name { foo: 42 }
            }
            fn act (&self) {
                let $myname = self;
                $block
            }
        }
    };
}

fn main() {
    make_struct!(myself: Test { println! ("Foo: {:?}", myself.foo); });
    let test = Test::new();
    test.act();
}


标签: rust