Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call methods on self in macros?

macro_rules! call_on_self {
    ($F:ident) => {
        self.$F()
    }
}

struct F;
impl F {
    fn dummy(&self) {}
    fn test(&self) {
        call_on_self!(dummy);
    }
}

The above doesn't work (Playground):

error[E0424]: expected value, found module `self`
  --> src/lib.rs:3:9
   |
3  |         self.$F()
   |         ^^^^ `self` value is a keyword only available in methods with `self` parameter
...
11 |         call_on_self!(dummy);
   |         --------------------- in this macro invocation

I don't understand why this is not working: the macro is invoked in the method where self is available! Is this somehow possible? Should I pass self into the macro because otherwise the macro cannot resolve self?

I'm using rustc 1.19.0-nightly.

like image 429
colinfang Avatar asked May 22 '17 19:05

colinfang


1 Answers

Rust macros are not just text replacement. Instead, there are a couple of important differences, one of which is "macro hygiene". With this, identifiers inside the macro don't interfere with identifiers outside which does prevent a couple of bugs that commonly happen with macro systems like C's.

As a consequence, a macro can only access identifiers that are:

  • passed explicitly, or
  • are in scope when the macro is defined.

While it might seem like an unnecessary restriction at first, it actually helps with the readability of code. Otherwise, it's "spooky action at a distance". It's more or less the same reasoning why passing references to a variable into a function is done via some_fn(&mut foo) in Rust and is not implicit like in C++ (some_fn(foo)): it's clearer how a variable is used by a function at the call site.

This means we have two ways to fix your problem. The standard solution is to pass in self to the macro:

macro_rules! call_on_self {
    ($self:ident, $F:ident) => {
        $self.$F()
    };
}

struct F;
impl F {
    fn dummy(&self) {}
    fn test(&self) {
        call_on_self!(self, dummy);
    }
}

If you only need to use the macro inside the test method, you can define the macro inside that method. Then, self is already in scope when the macro is defined, so it works without passing self:

struct F;
impl F {
    fn dummy(&self) {}
    fn test(&self) {
        macro_rules! call_on_self {
            ($F:ident) => {
                self.$F()
            };
        }

        call_on_self!(dummy);
    }
}

There's also fun combinations of the two, like defining a macro that takes self explicitly and another macro defined inside the function to capture self:

macro_rules! call_on_self_outer {
    ($self:ident, $F:ident) => {
        $self.$F()
    };
}

struct F;
impl F {
    fn dummy(&self) {}
    fn test(&self) {
        macro_rules! call_on_self {
            ($F:ident) => {
                call_on_self_outer!(self, $F);
            };
        }

        call_on_self!(dummy);
    }
}
`
like image 195
Shepmaster Avatar answered Sep 18 '22 09:09

Shepmaster