Reputation: 2135
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
.
Upvotes: 0
Views: 365
Reputation: 71605
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.
Upvotes: 1
Reputation: 22838
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 | }
...
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"}
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"}
Upvotes: 1