Scoping rules for macros

In the code below, I’ve defined macros get_int and add_int inside my process_whole function, which work as expected. I would like to use them unchanged in the process_part function in the same way, without duplicating them - but I can’t figure out where to move them to. If I move to the top level, I get

error[E0424]: expected value, found module `self`

and

error[E0425]: cannot find value `list` in this scope

How should the macros be defined so that they can be used in multiple places? Here’s the code:

extern crate rand; // 0.5.5

use rand::prelude::*;

struct Processor {
    counter: i32
}

impl Processor {
    fn new() -> Self {
        Processor{ counter: 0 }    
    }
    
    fn process_part(&mut self, list: &mut Vec<i32>, rng: &mut ThreadRng) {
        // would like to use get_int! and add_int! in here
        list.push(-1);
        list.push(rng.gen_range(-20, -10));
        list.push(-2);
    }
    
    fn process_whole(&mut self) {
        let mut list : Vec<i32> = vec![];
        let mut rng = thread_rng();
        
        macro_rules! get_int {
            ($rng: expr, $v: expr) => ({
                let mut r = &mut $rng;
                let v = $v;
                self.counter + v + r.gen_range(0, 10)
            });
        }
        
        macro_rules! add_int {
            ($i: expr) => ({
                let i = $i;
                
                list.push(i);
            });
        }
        
        for i in 0 .. 5 {
            let v = get_int!(rng, i);
            
            add_int!(v);
            if v >= 10 {
                self.process_part(&mut list, &mut rng);
            }
        }
        println!("{:?}", list);
    }
}

fn main() {
    let mut p = Processor::new();
    
    p.process_whole();
}

(Playground)

Output:

[6, 6, 6, 11, -1, -13, -2, 8]

Macros are not allowed to see anything from their environment that wasn’t explicitly passed to them as an argument.

But in my example, the macros, which are working as expected, “see” things that aren’t passed explicitly as arguments: get_int sees/uses self, and add_int sees/uses list, neither of which are passed as arguments to them. What’s the definition of “see” that you’re using?

I mean they behave exactly as if these variables didn’t exist at all, and the macro was not defined in the function, but at the top of the file. Move it and you’ll see it has the same error, because self outside of functions is a name of module (as in use self::foo).

I see that from the E0424 message - so I guess that macro templates are not simply textual (apart from the parameters passed in, of course). So the thing I’ll try is to add $self / $list parameters to the macros, I suppose.

1 Like

This is absolutely not true. Macros can “see” identifiers that were defined before the macro was, within the same scope. Also, macros are not “hoisted” out of the defining context as your second reply seems to imply.

The catch is that the identifiers a macro can see depends on its definition context, not its expansion context, which is why you can’t move these macros up and out of the function.

1 Like

Yes.

If the macros in question are more complex, you can simplify things by defining a top-level macro that requires everything to be passed explicitly, then define a shorthand macro inside the function that forwards things like self and list to the top-level macro for you.