0

I have defined the following macro because I need many slightly different structs with Serde serialization.

macro_rules! instantiate {
    (
        $(#[$struct_meta:meta])*
        $struct_name:ident {
            $(
                $(#[$field_meta:meta])*
                $field_name:ident: $field_type:ty = $field_value:expr,
            )+
        }
    ) => {{
        $(#[$struct_meta])*
        struct $struct_name {
            $(
                $(#[$field_meta])*
                $field_name: $field_type,
            )*
        }

        $struct_name {
            $($field_name: $field_value,)*
        }
    }};
}

And it should be used just like this:

instantiate! {
    #[derive(serde::Serialize)]
    RequestParams {
        #[serde(serialize_with="serialize_debug")]
        ids: Vec<Base62Uint> = version_ids
            .into_iter()
            .map(std::fmt::Display::to_string)
            .collect(),
    }
}

However, I have another use for it:

let (hash, kind) = match hash {
    FileHashes {
        sha512: Some(hash), ..
    } => (hash, "sha512"),
    FileHashes {
        sha1: Some(hash), ..
    } => (hash, "sha1"),
    _ => todo!(),
};

instantiate! {
    #[derive(serde::Serialize)]
    RequestParams {
        algorithm: &str = kind,
    }
}

I end up with the following error, which was expected:

error[E0106]: missing lifetime specifier
   --> src/endpoints.rs:236:28
    |
236 |                 algorithm: &str = kind,
    |                            ^ expected named lifetime parameter
    |
help: consider introducing a named lifetime parameter
    |
88  ~         struct $struct_name<'a> {
89  |             $(
90  |                 $(#[$field_meta])*
91  |                 $field_name: $field_type,
92  |             )*
93  |         }
  ...

I want this macro to create a new lifetime for every $field_name, where the corresponding $field_type is a reference.

Here is what I have attempted:

$field_name: $(&$field_life:lifetime)? $field_type,

And stopped because I realized that would only capture the input lifetimes, which is less ergonomic than what I want, and I would end up with an ambiguity in the pattern because it input would be able to match either the &$field_life:lifetime portion or the $field_type:ty.

Jacob Birkett
  • 1,927
  • 3
  • 24
  • 49
  • Your original macro already doesn't work for me. Please provid a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example), preferably tested on https://play.rust-lang.org/. – Finomnis Jun 11 '22 at 14:55
  • Apart of that, I suspect that replacing `&str` with `&'static str` should fix the problem – Finomnis Jun 11 '22 at 14:56

2 Answers2

1

I assume that your minimal reproducible example is

macro_rules! instantiate {
    (
        $(#[$struct_meta:meta])*
        $struct_name:ident {
            $(
                $(#[$field_meta:meta])*
                $field_name:ident: $field_type:ty = $field_value:expr,
            )+
        }
    ) => {{
        $(#[$struct_meta])*
        struct $struct_name {
            $(
                $(#[$field_meta])*
                $field_name: $field_type,
            )*
        }

        $struct_name {
            $($field_name: $field_value,)*
        }
    }};
}

fn main() {
    let x = instantiate! {
        #[derive(serde::Serialize)]
        RequestParams {
            #[serde(default)]
            id: u32 = 10,
        }
    };

    println!("{}", serde_json::to_string(&x).unwrap());
}
{"id":10}

and your second example is, with the macro being identical:

fn main() {
    let (hash, kind) = (1234, "sha512");

    let x = instantiate! {
        #[derive(serde::Serialize)]
        RequestParams {
            algorithm: &str = kind,
        }
    };

    println!("{}", serde_json::to_string(&x).unwrap());
}
error[E0106]: missing lifetime specifier
  --> src/main.rs:31:24
   |
31 |             algorithm: &str = kind,
   |                        ^ expected named lifetime parameter
   |
help: consider introducing a named lifetime parameter
   |
12 ~         struct $struct_name<'a> {
13 |             $(
14 |                 $(#[$field_meta])*
15 |                 $field_name: $field_type,
16 |             )*
17 |         }
 ...

Solution #1 - the cheap solution without capturing lifetimes

fn main() {
    let (hash, kind) = (1234, "sha512");

    let x = instantiate! {
        #[derive(serde::Serialize)]
        RequestParams {
            algorithm: &'static str = kind,
        }
    };

    println!("{}", serde_json::to_string(&x).unwrap());
}
{"algorithm":"sha512"}

Solution #2 - capturing lifetimes

macro_rules! instantiate {
    (
        $(#[$struct_meta:meta])*
        $struct_name:ident $(< $($lifetimes:lifetime),* >)? {
            $(
                $(#[$field_meta:meta])*
                $field_name:ident: $field_type:ty = $field_value:expr,
            )+
        }
    ) => {{
        $(#[$struct_meta])*
        struct $struct_name $(< $($lifetimes),* >)? {
            $(
                $(#[$field_meta])*
                $field_name: $field_type,
            )*
        }

        $struct_name {
            $($field_name: $field_value,)*
        }
    }};
}

fn main() {
    let (hash, kind) = (1234, "sha512".to_string());

    let x = instantiate! {
        #[derive(serde::Serialize)]
        RequestParams<'a> {
            algorithm: &'a str = &kind,
        }
    };

    println!("{}", serde_json::to_string(&x).unwrap());
}
{"algorithm":"sha512"}
Finomnis
  • 18,094
  • 1
  • 20
  • 27
  • I wanted the lifetimes to be captured off the fields and added automatically – Jacob Birkett Jun 11 '22 at 19:18
  • Then I think my answer is that `macro_rules` is not powerful enough, you might have to use [procedural macros](https://doc.rust-lang.org/book/ch19-06-macros.html) for that use case. – Finomnis Jun 11 '22 at 19:20
1

macro_rules! cannot generate lifetimes, for the same reasons they cannot generate identifiers (Can a Rust macro create new identifiers?), because they cannot (without help) generate new token. You have to use a procedural macro for that.

You can use existing procedural macros that allow you to generate tokens, for example @dtolnay's seq-macro crate, but it will raise another problem: how do you know where to put the lifetime? You can (partially, because you cannot identify all types requiring lifetimes) solve that by tt-munching and identifying types that require lifetimes (for example references), but this will make your macro really complex, even more than a proc macro. I do not recommend doing that.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • In your first sentence, you mention that macro rules can't generate lifetimes for the same reason they can't generate identifiers. Please add that reason to your answer, and link to the source, and this will be the answer I was looking for. – Jacob Birkett Jun 18 '22 at 06:45