3

This macro compiles when invoked:

macro_rules! remote_optional {
    ($remote:ident with=$def:ident $def_str:expr) => {
        impl $def {
            fn deserialize_option<'de, D>(deserializer: D) -> Result<Option<$remote>, D::Error>
            where
                D: Deserializer<'de>,
            {
                #[derive(Deserialize)]
                struct Wrapper(#[serde(with = $def_str)] $remote);

                let v: Option<Wrapper> = Option::deserialize(deserializer)?;
                Ok(v.map(|Wrapper(a)| a))
            }
        }
    }
}

This one doesn't:

macro_rules! remote_optional {
    ($remote:ident with=$def:ident) => {
        impl $def {
            fn deserialize_option<'de, D>(deserializer: D) -> Result<Option<$remote>, D::Error>
            where
                D: Deserializer<'de>,
            {
                #[derive(Deserialize)]
                struct Wrapper(#[serde(with = stringify!($def))] $remote);

                let v: Option<Wrapper> = Option::deserialize(deserializer)?;
                Ok(v.map(|Wrapper(a)| a))
            }
        }
    }
}

This is because stringify!($def) is passed into the #[serde(...)] attribute unevaluated.

Is there any practical workaround?

kpozin
  • 25,691
  • 19
  • 57
  • 76

1 Answers1

2

Could the macro of two arguments forward to the macro of three arguments, expanding the def identifier?

macro_rules! remote_optional {
    // The one that doesn't work (two arguments)
    // forwards to the one that *does* work, expanding the
    // string.
    ($remote:ident with=$def:ident) => {
        remote_optional!($remote, with=$def, stringify!($def));
    };

    // The macro that *does* work
    ($remote:ident with=$def:ident $def_str:expr) => {
        impl $def {
            fn deserialize_option<'de, D>(deserializer: D) -> Result<Option<$remote>, D::Error>
            where
                D: Deserializer<'de>,
            {
                #[derive(Deserialize)]
                struct Wrapper(#[serde(with = $def_str)] $remote);

                let v: Option<Wrapper> = Option::deserialize(deserializer)?;
                Ok(v.map(|Wrapper(a)| a))
            }
        }
    };
}

We could also consider making the macro of three arguments an implementation detail.

Little, isolated proof-of-concept:

macro_rules! my_macro {
    ($x:expr, $y:expr) => {
        my_macro!($x, $y, stringify!($x + $y));
    };

    ($x:expr, $y:expr, $msg:expr) => {
        println!("{} + {} = {}", $x, $y, $msg);
    };
}


fn main() {
    my_macro!(3, 2); // 3 + 2 = 3 + 2
}
mciantyre
  • 36
  • 1
  • 4
  • [Playground for little, isolated example.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=334f70b9630e33fa31d7ab9039d1fbcd) – mciantyre Apr 09 '19 at 00:43
  • 2
    This doesn't work, as the macro is just expanding to `println!("{} + {} = {}", $x, $y, stringify!($x + $y))` – Mingwei Samuel Jun 21 '22 at 03:03
  • 1
    Right, this only works if `stringify!` will be used in an expression context. It won't produce a string literal in e.g., `#[cfg(feature = $feature_string_literal)]`, as it'll expand to `#[cfg(feature = stringify!(feature_name))]` (instead of `#[cfg(feature = "feature_name")]`) which is not allowed. – BallpointBen Dec 09 '22 at 20:46