2

I'd like to read attributes programatically. For example, I have a struct which has attributes attached to each field:

#[derive(Clone, Debug, PartialEq, Message)]
pub struct Person {
    #[prost(string, tag="1")]
    pub name: String,
    /// Unique ID number for this person.
    #[prost(int32, tag="2")]
    pub id: i32,
    #[prost(string, tag="3")]
    pub email: String,
    #[prost(message, repeated, tag="4")]
    pub phones: Vec<person::PhoneNumber>,
}

(source)

I'd like to find the tag associated with the email field.

I expect there is some code like this to get the tag at runtime:

let tag = Person::email::prost::tag;
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
timthelion
  • 2,636
  • 2
  • 21
  • 30
  • 2
    Attributes are read only at compile-time. You must write a compiler plugin, *aka* proc-macro to do that. – Boiethios Feb 07 '19 at 12:20

1 Answers1

2

Since attributes are read only at compile time you need to write a procedural macro to solve such issue.

You can find information with following designators in your macro.

  • Field name with ident
  • Attribute contents with meta

After you find your field name and your meta, then you can match the stringified result with given input parameter in macro like following:

macro_rules! my_macro {
    (struct $name:ident {
        $(#[$field_attribute:meta] $field_name:ident: $field_type:ty,)*
    }) => {
        struct $name {
            $(#[$field_attribute] $field_name: $field_type,)*
        }

        impl $name {
            fn get_field_attribute(field_name_prm : &str) -> &'static str {
                let fields = vec![$(stringify!($field_name,$field_attribute)),*];
                let mut ret_val = "Field Not Found";

                fields.iter().for_each(|field_str| {
                    let parts : Vec<&str> = field_str.split(' ').collect();
                    if parts[0] == field_name_prm{
                        ret_val = parts[2];
                    }
                });
                ret_val
            }
        }
    }
}

my_macro! {
    struct S {
        #[serde(default)]
        field1: String,
        #[serde(default)]
        field2: String,
    }
}

Please note that it assumes that every field in the struct has an attribute. And every field declaration is ending with , including last field. But with some modification on regex you can make it available for optional attributes as well.

Here working solution in Playground

For further info about designators here is the reference

Also you can take a quick look for procedural macros here

Akiner Alkan
  • 6,145
  • 3
  • 32
  • 68
  • If I understand this correctly, this requires modifying the file with the attributes. In my case, that can be done, since prost generates the file in build.rs and I can just add another bit of code that runs sed or something, but it's not ideal. I also wonder, if I were to then polish this method and send it as a PR to the prost developer, whether this is actually the right way(tm) to do this. It seems very awkward. – timthelion Feb 07 '19 at 17:51
  • The code itself is not prost specific. It can be applied to any attribute value, so as in example it is serde attribute. The third party library of the attribute should not matter for that solution. – Akiner Alkan Feb 07 '19 at 18:34
  • 1
    It looks to me like this doesn't actually apply the attributes to the fields in the resulting `struct`. I think you need to change `$($field_name: $field_type,)*` to `$(#[$field_attribute] $field_name: $field_type)*`. – Michael Anderson Feb 08 '19 at 00:34
  • 1
    Also you need to watch out as you need to have a `,` after the last field (which isn't normally required on a struct). – Michael Anderson Feb 08 '19 at 00:37
  • @Michael Anderson, wow nice caught the mistakes, let mef ix attribute applying and noting the `,` out with an edit in the answer – Akiner Alkan Feb 08 '19 at 05:19