Creating a Macro in Rust to take a Struct and turning it into a Tuple of its field’s types in order.

huangapple go评论87阅读模式
英文:

Creating a Macro in Rust to take a Struct and turning into a Tuple of its field's types in order

问题

基本上,目标是能够获取一个类似以下结构的结构体:

  1. struct Test
  2. {
  3. id: i64,
  4. name: String,
  5. data: HashMap<String, String>,
  6. }

并且使宏返回一个元组,其中包含字段类型,按照我们声明test结构的字段顺序,如下所示:
这样它可以在以下上下文中使用,例如在scylla::IntoTypedRows中:

  1. rows.into_typed::<(get_field_types!(Test))>()

而不是当前的方式,即:

  1. rows.into_typed::<(i64, String, HashMap<String, String>)>()

这样,随着结构体变得更大,不会变得繁琐或臃肿。

我遇到的问题是我能够返回Vec<&str>,但似乎无法理解如何实现上述目标。以下是我的尝试:

  1. macro_rules! get_field_types {
  2. ($struct_name:ty) => {
  3. {
  4. let mut field_types = Vec::new();
  5. let _ = <$struct_name>::default(); // 确保实例化结构体
  6. let field_values = stringify!($struct_name {
  7. $(field: _type,)*
  8. ..
  9. });
  10. let field_pairs = field_values.split(',').filter_map(|field_value| {
  11. let field_type = field_value.split_whitespace().nth(1)?;
  12. Some(field_type.trim_end_matches(',').trim())
  13. });
  14. field_types.extend(field_pairs);
  15. field_types
  16. }
  17. };
  18. }
英文:

Basically, the goal is to be able to take a struct like the following:

  1. struct Test
  2. {
  3. id : i64,
  4. name : String,
  5. data : HashMap&lt;String, String&gt;,
  6. }

And have the macro return a tuple of its field types in the order that we declared the fields of test like follows:
So it could be used in the following context for example in scylla::IntoTypedRows;

  1. rows.into_typed::&lt;(get_field_types!(Test))&gt;()

instead of the current way it has to be done which is:

  1. rows.into_typed::&lt;(i64, String, HashMap&lt;String, String&gt;)&gt;()

That way as structs get larger, it does not become tedious or bulky.

The issue I have had has been that I am able to return a Vec<&str> but can't seem to reason how to get it to do the above. Here was my attempt:

  1. macro_rules! get_field_types {
  2. ($struct_name:ty) =&gt; {
  3. {
  4. let mut field_types = Vec::new();
  5. let _ = &lt;$struct_name&gt;::default(); // Ensure the struct is instantiated
  6. let field_values = stringify!($struct_name {
  7. $(field: _type,)*
  8. ..
  9. });
  10. let field_pairs = field_values.split(&#39;,&#39;).filter_map(|field_value| {
  11. let field_type = field_value.split_whitespace().nth(1)?;
  12. Some(field_type.trim_end_matches(&#39;,&#39;).trim())
  13. });
  14. field_types.extend(field_pairs);
  15. field_types
  16. }
  17. };
  18. }

答案1

得分: 1

PitaJ在这里是正确的,你应该使用#[derive(FromRow)]来做这个,然后使用rows.into_typed::<Test>(),因为这样可以避免维护宏的麻烦,使你的结构更加灵活。

为了学习的目的,以下是如何使用声明性宏来实现这一点。虽然宏目前无法执行查找(如果它们以后能够执行查找,我会感到惊讶),但你可以在结构声明中应用它们,像这样:

  1. use paste::paste;
  2. macro_rules! field_tuple {
  3. (
  4. $vis_:vis struct $name:ident {
  5. $($fvis_:vis $it:ident : $typ:ty, )*
  6. }
  7. ) => {
  8. $vis_ struct $name {
  9. $($fvis_ $it: $typ),*
  10. }
  11. paste! {
  12. $vis_ type [< $name FieldTypes >] = ($($typ),*);
  13. }
  14. };
  15. }
  16. enum Color {
  17. Pink,
  18. Blue,
  19. Black,
  20. White,
  21. }
  22. field_tuple!(struct Penguin {
  23. pub height: u32,
  24. pub(crate) weight: u8,
  25. color: Color,
  26. });
  27. #[cfg(test)]
  28. mod tests {
  29. use super::*;
  30. #[test]
  31. fn compiles() {
  32. let _: PenguinFieldTypes;
  33. }
  34. }

这使用了paste crate 来连接结构名和 FieldTypes 以生成一个类型定义,这样你就可以用它来定义多个字段元组类型,而不会互相冲突。

英文:

PitaJ is correct here that you should be doing this with #[derive(FromRow)] and using rows.into_typed::&lt;Test&gt;(), as it saves you the hassle of maintaining a macro, and makes your struct more flexible.

In the spirit of learning, here is how you could do this with a declarative macro. While macros can't do lookups currently (and I would be surprised if they ever would be able to), you can apply them at struct declaration like so:

  1. use paste::paste;
  2. macro_rules! field_tuple {
  3. (
  4. $vis_:vis struct $name:ident {
  5. $($fvis_:vis $it:ident : $typ:ty, )*
  6. }
  7. ) =&gt; {
  8. $vis_ struct $name {
  9. $($fvis_ $it: $typ),*
  10. }
  11. paste! {
  12. $vis_ type [&lt;$name FieldTypes&gt;] = ($($typ),*);
  13. }
  14. };
  15. }
  16. enum Color {
  17. Pink,
  18. Blue,
  19. Black,
  20. White,
  21. }
  22. field_tuple!(struct Penguin {
  23. pub height: u32,
  24. pub(crate) weight: u8,
  25. color: Color,
  26. });
  27. #[cfg(test)]
  28. mod tests {
  29. use super::*;
  30. #[test]
  31. fn compiles() {
  32. let _: PenguinFieldTypes;
  33. }
  34. }

This uses the paste crate in order to concatenate the struct name and FieldTypes in order to generate a type definition, so that you can use it to define multiple field-tuple types without conflicting with one another.

huangapple
  • 本文由 发表于 2023年6月13日 05:29:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/76460440.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定