How to turn off macro hygiene?

610 views Asked by At

I'm trying to make a macro that pastes in boilerplate code and I would like to use the variables it pastes in afterwards. Rust's macro hygiene is preventing this; how do I turn this off?

I understand that there are ways to work around it, by passing all the variables into the macro, but that would make the macro useless... I found a crate called unhygienic, which states it does exactly this, but I can't get it to work.

This is how it's supposed to work. This is how you can use the Processing (drawing/graphics framework) in Java or JavaScript (this would show a moving car (defined in a Car class) and a rectangle:

// You need to add a library or use the pde to be able to make this work though

let car;

setup() {
    createCanvas(500, 100);
    frameRate(60);
    car = new Car();
}

draw() {
    car.move();
    car.show();

    // this draws a rectangle at a certain place
    rect(100, 200, 200, 100)
}

setup is everything you do once before the main loop, and draw is everything you do inside the loop. This is how I would like to have it look in Rust (definitely not needed, just for aesthetics):

//does all the boilerplate opengl setup. 
setup!{
    // setting my variables and settings
}

// // adds a loop around the code in it and keeps up the framerate etc
draw!{
    // do my updating and drawing commands
}
1

There are 1 answers

3
Coder-256 On

Try using a trait! This shows the general outline of how you could implement this:

Playground

// Library:
mod library {
    pub trait Program {
        fn setup(ctx: &mut Context) -> Self;
        fn draw(&mut self, ctx: &mut Context);
    }

    pub struct Context;

    impl Context {
        pub fn create_canvas(&mut self, width: usize, height: usize) {
            // ...
        }

        pub fn frame_rate(&mut self, fps: usize) {
            // ...
        }

        pub fn rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
            // ...
        }
    }

    pub fn run_program<P: Program>() {
        let ctx = Context;
        let program = P::setup(&mut ctx);

        loop {
            program.draw(&mut ctx);
        }
    }
}

// User:
use library::{Context, Program};

struct Car;

impl Car {
    // `move` is a reserved keyword
    fn move_pos(&mut self, ctx: &mut Context) {
        // ...
    }

    fn show(&mut self, ctx: &mut Context) {
        // ...
    }
}

struct MyProgram {
    car: Car,
}

impl Program for MyProgram {
    fn setup(ctx: &mut Context) -> Self {
        ctx.create_canvas(500, 100);
        ctx.frame_rate(60);

        Self { car: Car }
    }

    fn draw(&mut self, ctx: &mut Context) {
        self.car.move_pos(ctx);
        self.car.show(ctx);

        // this draws a rectangle at a certain place
        ctx.rect(100, 200, 200, 100)
    }
}

fn main() {
    library::run_program::<MyProgram>();
}

I know it is a bit burdensome to pass ctx around everywhere, but it is well worth it because you don't have to worry about using a Rc<RefCell<...>> (which can get even more complicated with multiple threads) in order to ensure unique access to a shared context via slow, runtime-checked memory management. You can run multiple programs at the same time, each with its own context, and you know that at any given time, at most one function is writing to the context.