Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing nested struct field path as macro parameter

I am fairly new to Rust, and I am having trouble getting the following code to compile:

#![feature(trace_macros)]

fn main() {
    #[derive(Debug)]
    struct Inner {
      value: u8
    }
    
    #[derive(Debug)]
    struct Outer {
      inner: Inner
    }
    
    let mut x  = Outer { inner: Inner { value: 64 } };
    
    /********/
    
    macro_rules! my_macro {
        ($field_path:expr, $v:expr) => {
            x.$field_path = $v;
        }
    }

    trace_macros!(true);    
    // my_macro!(inner, Inner { value: 42 }); // only works with $field_path:ident
    my_macro!(inner.value, 42); // expected output: x.inner.value = 42;
    trace_macros!(false);
    
    x . inner.value = 42; // works fine
    
    assert_eq!(42, x.inner.value);
}

I am getting the following errors:

error: unexpected token: `inner.value`
  --> src/main.rs:20:15
   |
20 |             x.$field_path = $v;
   |               ^^^^^^^^^^^
...
26 |     my_macro!(inner.value, 42); // expected output: x.inner.value = 42;
   |     --------------------------- in this macro invocation
   |

...

error: expected one of `.`, `;`, `?`, `}`, or an operator, found `inner.value`
  --> src/main.rs:20:15
   |
20 |             x.$field_path = $v;
   |               ^^^^^^^^^^^ expected one of `.`, `;`, `?`, `}`, or an operator
...
26 |     my_macro!(inner.value, 42); // expected output: x.inner.value = 42;
   |     --------------------------- in this macro invocation
   |

...

However, trace_macro seems to be able to expand my_macro!:

note: trace_macro
  --> src/main.rs:26:5
   |
26 |     my_macro!(inner.value, 42); // expected output: x.inner.value = 42;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: expanding `my_macro! { inner . value, 42 }`
   = note: to `x . inner.value = 42 ;` <<< exactly what I am looking for

If I keep $field_path parameter as ident, I am simply getting no rules expected the token `.` , which I guess makes sense, because . is an operator. What am I missing?

Playground link

like image 981
Big Monday Avatar asked Oct 27 '25 10:10

Big Monday


1 Answers

I think the issue lies in the fact that inner.value contains the dot operator.

That's exactly your issue. There isn't a single fragment specifiers that allows you to match ident and/or a field access expression. The issue with using expr (expressions) is that when expanded it essentially wraps in parenthesis, i.e. x.(inner.value), which doesn't make sense and thus why you're getting an "unexpected token `inner.value`".

However, you can indeed use ident, you just need to use repetition as well.

In short, instead of $field_path:ident then you do $( $field_path:ident ).+. Then to expand it, instead of x.$field_path then you do x. $( $field_path ).+.

macro_rules! my_macro {
    ($($field_path:ident).+, $v:expr) => {
        x.$($field_path).+ = $v;
    };
}

// Now both are allowed
my_macro!(inner.value, 42);
my_macro!(inner, Inner { value: 64 });
like image 131
vallentin Avatar answered Oct 30 '25 07:10

vallentin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!