Optional brackets after an ident in a macro?

struct A {a: i32}
struct B {asdf: i32}
impl B {
    fn b (&self) -> i32 {
        self.asdf
    }
}
//struct C(i32);

macro_rules! get {
    ($x: ident, $access:ident$(())?) => {
       $x.$access$(())? 
    }
}


fn main() {
    let a: A = A{a: 1};
    let b: B = B{asdf: 2};
    //let c: C = C(3);
    dbg!(
        get!(a, a), 
        get!(b, b()),
        //get!(c, 0),
    );
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
  --> src/main.rs:12:19
   |
12 |        $x.$access$(())? 
   |                   ^^^^

error: could not compile `playground` (bin "playground") due to 1 previous error

You can't do this. An expansion has to have an actual captured variable, and you can't capture "nothing" or arbitrary literal tokens.

The simplest way to achieve what you want is to have two rules: one that ends in () and one that doesn't.

Another approach would be to use something like $access:ident $($tail:tt)*, with the expansion being $access $($tail)*?. This matches and substitutes anything at the end of the input. The downside is that it matches and substitutes absolutely anything at the end of the input.

What's the idiom to adapt this to multiple of these parameters? (so the tail doesn't match the next one)

In that case, you'd want two rules to match "ends in ();" and "ends in ;". Both of those rules would then need to capture "everything else" after the end of the first chunk using $($tail:tt)*, and recurse on that tail.

Basically, anywhere you want to repeat matching something you can't directly write as a single capture pattern, you need to use recursion and multiple rules (one for each different form).

I don't want to resort to tt munchers and recursion, so this is what I've got so far:

macro_rules! get {
    ($x:ident, {$access:ident$($brackets:tt)?}$(, {$access2:ident$($brackets2:tt)?})*) => {
       $x.$access$($brackets)? $(+ $x.$access2$($brackets2)?)* 
    }
}

get!(b, {b()}, {bb}),

Not the prettiest, but I do avoid an extra trailing comma.

That also works. :slight_smile:

One trick I have seen somewhere is to introduce a variable in a way that will (almost) never be matched:

macro_rules! get {
    ($x: ident, $access:ident$(()$(something_noone_will_guess $b:tt)?)?) => {
       $x.$access$(()$($b)?)? 
    }
}

(Playground)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.