Generalizing over members and methods in macros?

trait Getter<T> {
    fn get(&self) -> T;
}

struct A<T: Copy>(T);

struct B<T: Copy>{ val: T }

struct C<T: Copy>{ private_val: T }
impl<T: Copy> C<T> {
    pub fn new(val: T) -> Self {
        Self {private_val: val}
    }
    pub fn val(&self) -> T {
        self.private_val
    }
}

macro_rules! impl_getter_for {
    ($Struct:ident<T>, $accessor:ident) => {
        impl<T: Copy> Getter<T> for $Struct<T> {
            fn get(&self) -> T {
                self.$accessor
            }
        } 
        
    }
}

impl_getter_for!(A<T>, 0);
impl_getter_for!(B<T>, val);
impl_getter_for!(C<T>, val());

fn main() {
    let a: A<i32> = A(5);
    let b: B<i32> = B{ val: 6};
    let c: C<i32> = C::new(7);
    dbg!(a.get(),b.get(),c.get());
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error: no rules expected the token `0`
  --> src/main.rs:30:24
   |
19 | macro_rules! impl_getter_for {
   | ---------------------------- when calling this macro
...
30 | impl_getter_for!(A<T>, 0);
   |                        ^ no rules expected this token in macro call
   |
note: while trying to match meta-variable `$accessor:ident`
  --> src/main.rs:20:24
   |
20 |     ($Struct:ident<T>, $accessor:ident) => {
   |                        ^^^^^^^^^^^^^^^

error: no rules expected the token `(`
  --> src/main.rs:32:27
   |
19 | macro_rules! impl_getter_for {
   | ---------------------------- when calling this macro
...
32 | impl_getter_for!(C<T>, val());
   |                           ^ no rules expected this token in macro call
   |
note: while trying to match meta-variable `$accessor:ident`
  --> src/main.rs:20:24
   |
20 |     ($Struct:ident<T>, $accessor:ident) => {
   |                        ^^^^^^^^^^^^^^^

error[E0599]: no method named `get` found for struct `A` in the current scope
  --> src/main.rs:38:12
   |
5  | struct A<T: Copy>(T);
   | ----------------- method `get` not found for this struct
...
38 |     dbg!(a.get(),b.get(),c.get());
   |            ^^^ method not found in `A<i32>`
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following traits define an item `get`, perhaps you need to implement one of them:
           candidate #1: `Getter`
           candidate #2: `SliceIndex`

error[E0599]: no method named `get` found for struct `C` in the current scope
  --> src/main.rs:38:28
   |
9  | struct C<T: Copy>{ private_val: T }
   | ----------------- method `get` not found for this struct
...
38 |     dbg!(a.get(),b.get(),c.get());
   |                            ^^^ method not found in `C<i32>`
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following traits define an item `get`, perhaps you need to implement one of them:
           candidate #1: `Getter`
           candidate #2: `SliceIndex`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `playground` (bin "playground") due to 4 previous errors

Using $($accessor:tt)+ works for the playground (but may fall over in other use cases). I don't know if there's a better approach.

One step closer to needing to figure out procedural macros, but I'll take it.

Looks like there's parsing ambiguity when you need more than one of such parameters in the macro: Rust Playground

macro_rules! impl_getter_for {
    ($Struct:ident<T>, $($accessor1:tt)+, $($accessor2:tt)+) => {
        impl<T: Copy> Getter<T> for $Struct<T> {
            fn get(&self) -> T {
                self.$($accessor1)+ + self.$($accessor2)+  
            }
        } 
    }
}

Edit:

Would be very convenient if Rust allowed non-parameter optionals like this:Rust Playground

macro_rules! impl_getter_for {
    ($Struct:ident<T>, $accessor1:ident$(())?, $accessor2:ident$(())?) => {
        impl<T: Copy> Getter<T> for $Struct<T> {
            fn get(&self) -> T {
                self.$accessor1$(())? + self.$accessor2$(())?  
            }
        } 
    }
}

Edit 2:

Adding brackets allows it to work, but impacts ergonomics: Rust Playground

macro_rules! impl_getter_for {
    ($Struct:ident<T>, {$($accessor1:tt)+}, {$($accessor2:tt)+}) => {
        impl<T: RT> Getter<T> for $Struct<T> {
            fn get(&self) -> T {
                self.$($accessor1)+ + self.$($accessor2)+  
            }
        } 
    }
}

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.