Andrew Voelkel
Andrew Voelkel

Reputation: 589

Don't understand problem with nested Rust macro

I'm just getting going with Rust, and am trying to smooth over a couple inconveniences using macros. The first is that the derive Defaults macros don't support const functions so they don't support static objects. For this, I got help and adapted this macro I was given, it works well:

#[macro_export]
macro_rules! define_struct_with_const_defaults {
  ($v:vis struct $name:ident { $($f_name:ident: $f_type:ty = $f_default:expr),* $(,)? }) => {
      #[derive(Clone,Copy)]
      $v struct $name {
          $(
              $f_name: $f_type,
          )*
      }
      
      impl $name {
          pub const fn new() -> $name {
              $name {
                  $(
                      $f_name: $f_default,
                  )*
              }
          }
      }     
  }
}

So the next thing I wanted to do was get painless structure access for unit testing with python going. So I thought I could do something like this:

use pyo3::prelude::*;

#[macro_export]
macro_rules! define_struct_with_const_defaults {
  ($v:vis struct $name:ident { $($f_name:ident: $f_type:ty = $f_default:expr),* $(,)? }) => {
      #[derive(Clone,Copy)]
      #[pyclass]
      $v struct $name {
          $(
              #[pyo3(get, set)] $f_name: $f_type,
          )*
      }
      
      impl $name {
          pub const fn new() -> $name {
              $name {
                  $(
                      $f_name: $f_default,
                  )*
              }
          }
      }
  }
}

But that doesn't work, the complaint is "cannot find attribute pyclass in this scope"

On the other hand, if I do this, it works:

use pyo3::prelude::*;

#[pyclass]
struct bar {
  baz: i32,
}

This is beyond my level of understanding. It seems to be related to nesting macros, I'm wondering whether it has something to do with order of evaluation.

Eventually, I'd like to conditionally compile the pyo3 stuff with a config macro, depending on whether I'm in a unit test environment, but I can't even get this far ...

Any help would be appreciated in understanding what is going on here.

Upvotes: 0

Views: 460

Answers (1)

cafce25
cafce25

Reputation: 27186

The problem is probably that the macro will insert #[pyclass] at the callsite of it. So you have to put your use pyo3::prelude::*; there or even better include the full path in the macro to avoid name conflicts:

#[macro_export]
macro_rules! define_struct_with_const_defaults {
  ($v:vis struct $name:ident { $($f_name:ident: $f_type:ty = $f_default:expr),* $(,)? }) => {
      #[derive(Clone,Copy)]
      #[::pyo3::prelude::pyclass]
      $v struct $name {
          $(
              #[pyo3(get, set)] $f_name: $f_type,
          )*
      }
      
      impl $name {
          pub const fn new() -> $name {
              $name {
                  $(
                      $f_name: $f_default,
                  )*
              }
          }
      }
  }
}

Upvotes: 1

Related Questions