Is it okay to use "let $arg =" within a macro to shadow the previous definition of $arg outside the macro?

I have the following in my code (simplified). Is it okay to use let in such a way to "hijack" the outer variable outside the macro ? It works fine, but somehow feels like a hack...

macro_rules! foo {
    ($gd: ident, $elt: ident) => {
        let $gd = || { $gd(); println!("{}", stringify!($elt)); };
    };
}

fn main() {
    let gd = || println!("First!");
    foo!(gd, ThisIsTheOne);
    foo!(gd, TwoIsTwo);
    foo!(gd, Three);
    gd();
}

(Playground)

The real code uses this trick to inline-create various structures, then alter their values and in the end be able to automagically collect them into a mustache MapBuilder, so the corresponding macro looks as follows:


macro_rules! html_text {
    ($gd: ident, $elt: ident, $rinfo: ident, $modified: ident) => {
        let mut $elt: std::rc::Rc<std::cell::RefCell<HtmlText>> =
            std::rc::Rc::new(std::cell::RefCell::new(Default::default()));
        {
            let mut $elt = $elt.borrow_mut();
            $elt.highlight = $rinfo.state.$elt != $rinfo.initial_state.$elt;
            $elt.value = $rinfo.state.$elt.clone().to_string();
            $elt.id = format!("{}", stringify!($elt));
            $modified = $modified || $elt.highlight;
        }

        let $gd = || {
            $gd()
                .insert(stringify!($elt), &$elt.borrow().clone())
                .unwrap()
        };
    };
}

Yes, in fact this is exactly how pin_utils::pin_mut works!

2 Likes

In fact, the macro cannot access the gd binding defined in main() because of hygiene rules.
The only information macros have access to is the information passed to them directly by means of arguments.

Maybe I misunderstand what you wrote, but the code generated by macro can both call the previous value of $gd which is passed as parameter, as well as effectively redefine it, so the next invocation of the macro can stitch the call to this new value. You can see yourself in the playground example I linked. That’s what caused me to ask this question - it works just fine, I just wanted to ensure I am not accidentally exploiting some bug :slight_smile:

Yes, it works because your are working on a variable (ident) provided by the one calling the macro, which thereby grants the macro access to the name itself (not just the value) :slightly_smiling_face:

On the other hand, although rebinding is a very Rusty pattern, therefore not only acceptable but also sometimes needed when within a macro, I personally appreciate when the macro makes it explicit that it is performing a (re)binding. This is usually achieved by requiring some special syntax as input, such as:

macro_rules! foo {(
    $elt:ident => $gd:ident
) => (
    let $gd = || { $gd(); println!("{}", stringify!($elt)); };
)}

fn main ()
{
    let gd = || println!("First!");
    foo!(ThisIsTheOne => gd);
    foo!(TwoIsTwo => gd);
    foo!(Three => gd);
    gd();
}

Aside

You can get rid of some .clone() calls (before calling .to_string() or when taking a & reference), the first elt does not need to be mut if you are using RefCell.

You could even do:

macro_rules! html_text {(
    $gd:ident, $elt:ident, $rinfo:ident, $modified:ident $(,)?
) => (
    let highlight: bool = $rinfo.state.$elt != $rinfo.initial_state.$elt;
    let $elt = ::std::rc::Rc::new(::std::cell::RefCell::new(HtmlText {
        highlight,
        value: $rinfo.state.$elt.to_string(),
        id: stringify!($elt).into(),
        .. Default::default()
    }));

    $modified = $modified || highlight;

    let $gd = || {
        $gd()
            .insert(stringify!($elt), &*$elt.borrow())
            .unwrap()
    };
)}
  • Regarding hygiene, the let highlight = ... binding won't pollute the caller namespace, for instance.
1 Like

This is very cool, I had no clue about the “=>” syntax - I will try to redo it precisely for the same reasons you mention! Thanks so much!

Context: Highlight is a property of the struct embedding the html input element. This is a part of a tweak/rewrite of my web framework library. The very first iteration which wasn’t opensource had way too much boilerplate; the first shot at opensourcing it (the demo/test use of it is in github.com/ayourtch/slrsp) went a bit too heavy on the syn/quote usage; now I am making another, more balanced iteration, which won’t have the separate derive crate but rather (maybe) an external command to generate the needed boilerplate + some simpler but easier macros.

The “highlight” is a universal attribute for an input element whenever its state has been changed from initial, I found it was a great thing from the UX standpoint. It gets translated into uniform style change by the mustache template layer.

I think the principle - having a triple of (initial default state; current state; current default state) passed around with every event - is solid, and allows completely stateless processing on the backend, hence my stubbornness at trying to make this better :slight_smile: - thanks a lot for helping! :slight_smile:

1 Like