Way to crate multiple structs
Closed this issue · 3 comments
Would there be a way to create a struct with a different name using this crate. For example if you wanted to keep the original struct and make a new modified struct. The below does not work but hopefully gives you an idea of what I'm trying to do:
macro_rules! foo {
(
$(#[$struct_meta:meta])*
$struct_vis:vis
struct $StructName:ident {
$(
$(#[$field_meta:meta])*
$field_vis:vis $field_name:ident : $field_ty:ty
),* $(,)?
}
) => (
// generate the same struct definition
$(#[$struct_meta])*
$struct_vis
struct $StructName {
$(
$(#[$field_meta])*
$field_vis
$field_name: $field_ty,
)*
}
// new struct for private fields
struct $StructName_Private {
$(
$(#[$field_meta])*
$field_name: $field_ty,
)*
}
)}
Some context about the lack of a concat_idents!
operation
So, the operation you are describing is identifier concatenation: basically a concat_idents!($StructName, _Private)
kind of operation, in a similar way to doing concat!(stringify!($StructName), "_Private")
when concatenating strings.
And while this is possible to write and implement through a procedural macro (so simple the stdlib could have implemented it as well), the issue is that such a macro would still not be very useful, since Rust restricts the kinds of places where one can perform a macro invocation to start with!
That is, while
let foobar = 42;
let x = concat_idents!(foo, bar); // Would be OK
one cannot write:
struct macro_invocation!(…) {
fields…
}
and thus one can't write:
struct concat_idents!($StructName, _Private) {
fields…
}
either.
The solution, then, taken by https://docs.rs/paste, and other similar implementations, is to require that the macro invocation wrap the whole item (struct
) definition(s), since that's a valid macro call-site, and then have way to signify to that general macro where/when/how one wants to perform the identifier concatenation (it is thus what is called a "preprocessor pattern"). In the case of ::paste
, such intent is signified through the [< … >]
syntax:
How to use ::paste::paste!
to achieve this
+ // #[doc(hidden)] /** Not part of the public API */ pub // <- uncomment if public macro
+ use ::paste::paste as __paste;
macro_rules! foo {
(
$(#[$struct_meta:meta])*
$struct_vis:vis
struct $StructName:ident {
$(
$(#[$field_meta:meta])*
$field_vis:vis $field_name:ident : $field_ty:ty
),* $(,)?
}
) => (
// generate the same struct definition
$(#[$struct_meta])*
$struct_vis
struct $StructName {
$(
$(#[$field_meta])*
$field_vis
$field_name: $field_ty,
)*
}
+ $crate::__paste! {
// new struct for private fields
- struct $StructName_Private {
+ struct [< $StructName _Private >] {
$(
$(#[$field_meta])*
$field_name: $field_ty,
)*
}
+ }
);
}
A way to XY the problem (sometimes)
Sometimes you can get away with not needing a new identifier, at least for seemingly private-ish type definitions such as yours: use a const _: () = { … };
"block" / "scope" / "anonymous mod
ule" to make sure the names you define in that scope don't "leak" outside of your macro invocation, so as to avoid clashes, and then you can use some hard-coded name for that. In order to keep the new type's definition name readable (e.g., for documentation), what I personally do sometimes is to use
-rename the originally-defined type as some hard-coded name, and use the user-provided name for my helper struct:
macro_rules! foo {
(
$(#[$struct_meta:meta])*
$struct_vis:vis
struct $StructName:ident {
$(
$(#[$field_meta:meta])*
$field_vis:vis $field_name:ident : $field_ty:ty
),* $(,)?
}
) => (
// generate the same struct definition
$(#[$struct_meta])*
$struct_vis
struct $StructName {
$(
$(#[$field_meta])*
$field_vis
$field_name: $field_ty,
)*
}
+ const _: () = {
+ use $StructName as __Input;
+
+ const _: () = {
// new struct for private fields
struct $StructName {
$(
$(#[$field_meta])*
$field_name: $field_ty,
)*
}
impl SomeTrait for __Input { type Assoc = $StructName; … } // for instance
+ };
+ };
);
}
In general using ::paste
(or a similar helper crate) is definitely the best approach, but this can be nifty trick to keep up one's sleeve 😉
I'm gonna close this issue since there isn't, AFAIK, anything for me to do with the crate, but feel free to ask further questions, they are always welcome. I've noticed I hadn't set up the Discussions
space for this repo, so I've just done that: it could be a more suitable space to discuss things in the future 🙂
Yes, I definitely tag that wrong as a issue
Thank you so much for you very quick and thorough response. It was very helpful.