Derive macro to annotate struct variables

I want to write a derive macro which generates following string from below struct.
I want to generate prometheus metric by just annotating variables.

# HELP auth label       
# TYPE auth_errors counter
auth_errors 4

Please note:
doc comment(/// auth label) to create 1st line. //# HELP auth label
metric_type to create 2nd line. //# TYPE auth_errors counter
variable name creates last line. //auth_errors 4

use my_proc_macro::Metric;
#[derive(Metric)]
pub struct Stats {
    /// auth label
    #[metric_type("counter")]
    #[serde(foo)]
    auth_errors: u64,
}

I am not able to find any document(rust books) how to annotate struct fields using derive macro.

The custom Derive macros are explained in the book, in the section Macros.

You can also find information in the reference, in the section Procedural Macros.

Thanks. I have gone thru both urls you pointed to earlier.

But I cannot find information regarding annotating struct variables. Even rust book does not have complete example of using derive macro.
I feel " Derive macro helper attributes" might help but there is not code on how to use them.

A Rust macro, such as a derive macro, is just a tool that shall write other Rust code in your stead.

So, when asking a question about a proc-macro, it should be phrased in the form of:

  1. how can I convert the following Rust input:

  2. into the following Rust output:

    This is currently missing

Indeed,

is no Rust code.


If you really don't want to / can't generate Rust code, but need to generate some other format of data off Rust code, in that case a procedural macro is not really the right tool.

That being said, it could be a hacky way to achieve it, by abusing the fact a procedural macro can currently generate a file. But the usual way is to havea CLI tool that will "scan" the Rust code and try to generate its own annotations off it, like wasm-bindgen does, or generate a Rust function that shall emit the needed annotation


Writing a derive that generates an associated function for the prometheus annotation

  1. Abstract the requirement as a trait:

    trait PrometheusAnnotations {
         fn prometheus_annotations() -> &'static str;
    }
    
    • Note: this could be optimized to take a Write or something, but that's an orthogonal concern to that of the macro.
  2. Write a derive for it: #[derive(PrometheusAnnotations)], that shall implement the trait for your type.

  3. Then, in some helper function (e.g., a dedicated unit-test, or a specific binary or example target attached to the current crate), you'd do:

    let mut file: File = …;
    file.write_str(Stats::prometheus_annotations())?;
    file.write_str(SomethingElse::prometheus_annotations())?;
    

    etc.

Regarding writing such a derive, at this point it's mostly a question of following the aforementioned guides.

The main points, w.r.t. the derive itself, assuming a syn with the "full" features (easier), would be:

#![allow(nonstandard_style, unused_imports)]

use ::proc_macro::{
    TokenStream,
};
use ::proc_macro2::{
    Span,
    TokenStream as TokenStream2,
};
use ::quote::{
    format_ident,
    quote, quote_spanned,
    ToTokens,
};
use ::syn::{*,
    parse::{Parse, Parser, ParseStream},
    punctuated::Punctuated,
    spanned::Spanned,
    Result,
};

/// The `= "…"` in a `#[doc = "…"]` attribute,
/// _i.e._, the `…` in `/// …`.
struct DocString(String);

impl Parse for DocString {
    fn parse (input: ParseStream<'_>)
      -> Result<DocString>
    {
        let _: Token![=] = input.parse()?;
        let line: LitStr = input.parse()?;
        Ok(Self(line.value()))
    }
}

#[proc_macro_derive(PrometheusAnnotations, attributes(metric_type))] pub
fn __ (input: TokenStream)
  -> TokenStream
{
    prometheus_annotations(input.into())
        .unwrap_or_else(|err| err.to_compile_error())
        .into()
}

fn prometheus_annotations (
    input: TokenStream2,
) -> Result<TokenStream2>
{
    use ::core::fmt::Write;

    let input: ItemStruct = parse2(input)?;
    let mut prometheus_string = String::new();
    for field in &input.fields {
        let attrs = &field.attrs;
        let doc_strings =
            attrs.iter().filter_map(|attr| bool::then(
                attr.path.is_ident("doc"),
                || parse2::<DocString>(attr.tokens.clone()).ok(),
            ).flatten())
        ;
        doc_strings.for_each(|DocString(doc_string)| {
            writeln!(prometheus_string, "HELP {doc_string}").unwrap();
        });

        let mut metric_types = attrs.iter().filter(|attr| attr.path.is_ident("metric_type"));
        match (metric_types.next(), metric_types.next()) {
            | (Some(attr), None) => {
                let metric_type: String = attr.parse_args::<LitStr>()?.value();
                writeln!(prometheus_string, "TYPE {metric_type}").unwrap();
            },
            | (None, None) => {
                // No `metric_type` provided: error or ignore?
                …
            },
            | (Some(_), Some(duplicate)) => return Err(Error::new_spanned(
                // code to highlight / blame
                duplicate,
                // error message
                "Duplicate `metric_type` attribute",
            )),
        };
        if let Some(ref field_name) = field.ident {
            writeln!(prometheus_string, "{field_name} 4").unwrap();
        } else {
            return Err(Error::new(Span::call_site(), "expected a braced struct"));
        }
        
    }

    let (intro_generics, fwd_generics, where_clause) = input.generics.split_for_impl();
    let StructName @ _ = &input.ident;
    let pub_ = &input.vis;

    Ok(quote!(
        impl #intro_generics
            // ::my_crate::PrometheusAnnotations for
            #StructName #fwd_generics
        #where_clause
        {
            #pub_ // <- remove if implementing trait rather than inherent function.
            fn prometheus_annotations ()
              -> &'static ::core::primitive::str
            {
                #prometheus_string
            }
        }
    ))
}
2 Likes

Thanks for help

error: there is no argument named `doc_string`
   --> into_map_derive\src\lib.rs:131:47
    |
131 |             writeln!(prometheus_string, "HELP {doc_string}").unwrap();     
    |                                               ^^^^^^^^^^^^

error: there is no argument named `metric_type`
   --> into_map_derive\src\lib.rs:138:51
    |
138 |                 writeln!(prometheus_string, "TYPE {metric_type}").unwrap();
    |                                                   ^^^^^^^^^^^^^

error: there is no argument named `field_name`
   --> into_map_derive\src\lib.rs:152:42
    |
152 |             writeln!(prometheus_string, "{field_name} 4").unwrap();
    |                                          ^^^^^^^^^^^^

error[E0432]: unresolved import `proc_macro2`
  --> into_map_derive\src\lib.rs:75:7
   |
75 | use ::proc_macro2::{
   |       ^^^^^^^^^^^ could not find `proc_macro2` in the list of imported crates

error[E0432]: unresolved import `quote::format_ident`
  --> into_map_derive\src\lib.rs:80:5
   |
80 |     format_ident,
   |     ^^^^^^^^^^^^ no `format_ident` in the root

error[E0412]: cannot find type `ItemStruct` in this scope
   --> into_map_derive\src\lib.rs:120:16
    |
120 |     let input: ItemStruct = parse2(input)?;
    |                ^^^^^^^^^^ not found in this scope

Of course, you have to declare every used crate in Cargo.toml - here, it's proc-macro2, syn (with features = ["full"]) and quote.

2 Likes
  • quote does not support full feature.
  • proc_macro2 does not support full feature.
[dependencies]
syn = { version="0.15.22", features = ["full"] }
# syn = { version = "0.15.22" }
#quote = { version="0.6.10" }
quote = { version="1.0.2", , features = ["full"] }
into_map = { path="../into_map" }
proc-macro2 = { version = "1.0.36", features = ["full"] }

depends on `quote`, with features: `full` but `quote` does not have these features.

depends on `proc-macro2`, with features: `full` but `proc-macro2` does not have these features.  

failed to select a version for `proc-macro2` which could resolve this conflict

$ Cargo.toml

Cargo.toml
[lib]
proc-macro = true

[dependencies]
syn = { version="0.15.22", features = ["full"] }
# syn = { version = "0.15.22" }
#quote = { version="0.6.10" }
quote = { version="1.0.2" }
into_map = { path="../into_map" }
proc-macro2 = { version = "1.0.36" }

$ lib.rs

#![allow(nonstandard_style, unused_imports)]

use ::proc_macro::{
    TokenStream,
};

use ::proc_macro2::{
    Span,
    TokenStream as TokenStream2,
};
use ::quote::{
    format_ident,
    quote, quote_spanned,
    ToTokens,
};
use ::syn::{*,
    parse::{Parse, Parser, ParseStream},
    punctuated::Punctuated,
    spanned::Spanned,
    Result,
};

/// The `= "…"` in a `#[doc = "…"]` attribute,
/// _i.e._, the `…` in `/// …`.
struct DocString(String);

impl Parse for DocString {
    fn parse (input: ParseStream<'_>)
      -> Result<DocString>
    {
        let _: Token![=] = input.parse()?;
        let line: LitStr = input.parse()?;
        Ok(Self(line.value()))
    }
}

#[proc_macro_derive(PrometheusAnnotations, attributes(metric_type))] pub
fn __ (input: TokenStream)
  -> TokenStream
{
    prometheus_annotations(input.into())
        .unwrap_or_else(|err| err.to_compile_error())
        .into()
}

fn prometheus_annotations (
    input: TokenStream2,
) -> Result<TokenStream2>
{
    use ::core::fmt::Write;

    let input: ItemStruct = parse2(input)?;
    let mut prometheus_string = String::new();
    for field in &input.fields {
        let attrs = &field.attrs;
        let doc_strings =
            attrs.iter().filter_map(|attr| bool::then(
                attr.path.is_ident("doc"),
                || parse2::<DocString>(attr.tokens.clone()).ok(),
            ).flatten())
        ;
        doc_strings.for_each(|DocString(doc_string)| {
            //writeln!(prometheus_string, "HELP {doc_string}").unwrap();
            writeln!(prometheus_string, "HELP auth label").unwrap();
        });

        let mut metric_types = attrs.iter().filter(|attr| attr.path.is_ident("metric_type"));
        match (metric_types.next(), metric_types.next()) {
            | (Some(attr), None) => {
                let metric_type: String = attr.parse_args::<LitStr>()?.value();
                //writeln!(prometheus_string, "TYPE {metric_type}").unwrap();
                writeln!(prometheus_string, "TYPE counter").unwrap();
            },
            | (None, None) => {
                // No `metric_type` provided: error or ignore?
                //…
            },
            | (Some(_), Some(duplicate)) => return Err(Error::new_spanned(
                // code to highlight / blame
                duplicate,
                // error message
                "Duplicate `metric_type` attribute",
            )),
            (None, Some(_)) => todo!(),
        };

        if let Some(ref field_name) = field.ident {
            //writeln!(prometheus_string, "{field_name} 4").unwrap();
            writeln!(prometheus_string, "auth_errors 4").unwrap();
        } else {
            return Err(Error::new(Span::call_site(), "expected a braced struct"));
        }
        
    }

    let (intro_generics, fwd_generics, where_clause) = input.generics.split_for_impl();
    let StructName @ _ = &input.name;
    let pub_ = &input.vis;

    Ok(quote!(
        impl #intro_generics
            // ::my_crate::PrometheusAnnotations for
            #StructName #fwd_generics
        #where_clause
        {
            #pub_ // <- remove if implementing trait rather than inherent function.
            fn prometheus_annotations ()
              -> &'static ::core::primitive::str
            {
                #prometheus_string
            }
        }
    ))
}

$ cargo run

error[E0308]: mismatched types
   --> into_map_derive\src\lib.rs:111:31
    |
111 |         .unwrap_or_else(|err| err.to_compile_error())
    |                               ^^^^^^^^^^^^^^^^^^^^^^ expected struct `proc_macro2::TokenStream`, found struct `TokenStream2`
    |
    = note: perhaps two different versions of crate `proc_macro2` are being used?

error[E0308]: mismatched types
   --> into_map_derive\src\lib.rs:121:36
    |
121 |     let input: ItemStruct = parse2(input)?;
    |                                    ^^^^^ expected struct `TokenStream2`, found struct `proc_macro2::TokenStream`
    |
    = note: perhaps two different versions of crate `proc_macro2` are being used?

error[E0609]: no field `tokens` on type `&Attribute`
   --> into_map_derive\src\lib.rs:128:45
    |
128 |                 || parse2::<DocString>(attr.tokens.clone()).ok(),
    |                                             ^^^^^^ unknown field
    |
    = note: available fields are: `pound_token`, `style`, `bracket_token`, `path`, `tts`

error[E0599]: no method named `parse_args` found for reference `&Attribute` in the current scope
   --> into_map_derive\src\lib.rs:139:48
    |
139 |                 let metric_type: String = attr.parse_args::<LitStr>()?.value();
    |                                                ^^^^^^^^^^ method not found in `&Attribute`

error[E0308]: mismatched types
   --> into_map_derive\src\lib.rs:160:35
    |
160 |             return Err(Error::new(Span::call_site(), "expected a braced struct"));
    |                                   ^^^^^^^^^^^^^^^^^ expected struct `syn::export::Span`, found struct `proc_macro2::Span`
    |
    = note: perhaps two different versions of crate `proc_macro2` are being used?

error[E0609]: no field `name` on type `syn::ItemStruct`
   --> into_map_derive\src\lib.rs:166:33
    |
166 |     let StructName @ _ = &input.name;
    |                                 ^^^^ unknown field
    |
    = note: available fields are: `attrs`, `vis`, `struct_token`, `ident`, `generics` ... and 2 others

error[E0277]: the trait bound `ImplGenerics<'_>: quote::ToTokens` is not satisfied
   --> into_map_derive\src\lib.rs:169:8
    |
169 |       Ok(quote!(
    |  ________^
170 | |         impl #intro_generics
171 | |             // ::my_crate::PrometheusAnnotations for
172 | |             #StructName #fwd_generics
...   |
181 | |         }
182 | |     ))
    | |_____^ the trait `quote::ToTokens` is not implemented for `ImplGenerics<'_>`
    |
    = note: required by `quote::ToTokens::to_tokens`
    = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `TypeGenerics<'_>: quote::ToTokens` is not satisfied
   --> into_map_derive\src\lib.rs:169:8
    |
169 |       Ok(quote!(
    |  ________^
170 | |         impl #intro_generics
171 | |             // ::my_crate::PrometheusAnnotations for
172 | |             #StructName #fwd_generics
...   |
181 | |         }
182 | |     ))
    | |_____^ the trait `quote::ToTokens` is not implemented for `TypeGenerics<'_>`
    |
    = note: required by `quote::ToTokens::to_tokens`
    = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `WhereClause: quote::ToTokens` is not satisfied
   --> into_map_derive\src\lib.rs:169:8
    |
169 |       Ok(quote!(
    |  ________^
170 | |         impl #intro_generics
171 | |             // ::my_crate::PrometheusAnnotations for
172 | |             #StructName #fwd_generics
...   |
181 | |         }
182 | |     ))
    | |_____^ the trait `quote::ToTokens` is not implemented for `WhereClause`
    |
    = note: required because of the requirements on the impl of `quote::ToTokens` for `&WhereClause`
    = note: 1 redundant requirements hidden
    = note: required because of the requirements on the impl of `quote::ToTokens` for `Option<&WhereClause>`
    = note: required by `quote::ToTokens::to_tokens`
    = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `Visibility: quote::ToTokens` is not satisfied
   --> into_map_derive\src\lib.rs:169:8
    |
169 |       Ok(quote!(
    |  ________^
170 | |         impl #intro_generics
171 | |             // ::my_crate::PrometheusAnnotations for
172 | |             #StructName #fwd_generics
...   |
181 | |         }
182 | |     ))
    | |_____^ the trait `quote::ToTokens` is not implemented for `Visibility`
    |
    = note: required because of the requirements on the impl of `quote::ToTokens` for `&Visibility`
    = note: required by `quote::ToTokens::to_tokens`
    = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 10 previous errors

You may be using an older version of Rust, which does not feature implicit(ly) named arguments for format strings. In that case you can fix it by explicitly adding the named argument yourself:

- writeln!(prometheus_string, "HELP {doc_string}").unwrap();
+ writeln!(prometheus_string, "HELP {doc_string}", doc_string = doc_string).unwrap();

which can then be simplified down to:

writeln!(prometheus_string, "HELP {}", doc_string).unwrap();

+

Both of these things are symptoms of having been using incorrect versions of the quote/proc-macro2/syn troika: either outdated, or incompatible with one another.

I should have clarified the Cargo.toml requirements, but better late than never :slightly_smiling_face::

# Cargo.toml
[lib]
proc-macro = true

[package]
# …

[dependencies]
# your own deps, such as:
into_map.path = "../into_map"  # you'll need a `.version` entry as well if/when publishing to crates.io

# The "Official" Proc-Macro-Writing Toolkit™ (not necessary, but it greatly simplifies writing down procedural macros).
proc-macro2.version = "1.0.37"
quote.version = "1.0.17"
syn.version = "1.0.91"
syn.features = ["full"]
2 Likes

@Yandros Thanks for help!
$ lib.rs

#![allow(nonstandard_style, unused_imports)]

use ::proc_macro::{
    TokenStream,
};

use ::proc_macro2::{
    Span,
    TokenStream as TokenStream2,
};
use ::quote::{
    format_ident,
    quote, quote_spanned,
    ToTokens,
};
use ::syn::{*,
    parse::{Parse, Parser, ParseStream},
    punctuated::Punctuated,
    spanned::Spanned,
    Result,
};

/// The `= "…"` in a `#[doc = "…"]` attribute,
/// _i.e._, the `…` in `/// …`.
struct DocString(String);

impl Parse for DocString {
    fn parse (input: ParseStream<'_>)
      -> Result<DocString>
    {
        let _: Token![=] = input.parse()?;
        let line: LitStr = input.parse()?;
        Ok(Self(line.value()))
    }
}

#[proc_macro_derive(PrometheusAnnotations, attributes(metric_type))] pub
fn __ (input: TokenStream)
  -> TokenStream
{
    prometheus_annotations(input.into())
        .unwrap_or_else(|err| err.to_compile_error())
        .into()
}

fn prometheus_annotations (
    input: TokenStream2,
) -> Result<TokenStream2>
{
    use ::core::fmt::Write;

    let input: ItemStruct = parse2(input)?;
    let mut prometheus_string = String::new();
    for field in &input.fields {
        let attrs = &field.attrs;
        let doc_strings =
            attrs.iter().filter_map(|attr| bool::then(
                attr.path.is_ident("doc"),
                || parse2::<DocString>(attr.tokens.clone()).ok(),
            ).flatten())
        ;
        doc_strings.for_each(|DocString(doc_string)| {
            writeln!(prometheus_string, "HELP {}", doc_string).unwrap();
            //writeln!(prometheus_string, "HELP auth label").unwrap();
        });

        let mut metric_types = attrs.iter().filter(|attr| attr.path.is_ident("metric_type"));
        match (metric_types.next(), metric_types.next()) {
            | (Some(attr), None) => {
                let metric_type: String = attr.parse_args::<LitStr>()?.value();
                writeln!(prometheus_string, "TYPE {metric_type}").unwrap();  // there is no argument named `metric_type`
                //writeln!(prometheus_string, "TYPE counter").unwrap();
            },
            | (None, None) => {
                // No `metric_type` provided: error or ignore?
                //…
            },
            | (Some(_), Some(duplicate)) => return Err(Error::new_spanned(
                // code to highlight / blame
                duplicate,
                // error message
                "Duplicate `metric_type` attribute",
            )),
            (None, Some(_)) => todo!(),
        };

        if let Some(ref field_name) = field.ident {
            writeln!(prometheus_string, "{field_name} 4").unwrap();  //there is no argument named `field_name`
            //writeln!(prometheus_string, "auth_errors 4").unwrap();
        } else {
            return Err(Error::new(Span::call_site(), "expected a braced struct"));
        }
        
    }

    let (intro_generics, fwd_generics, where_clause) = input.generics.split_for_impl();
    let StructName @ _ = &input.name;          //unknown field
    let pub_ = &input.vis;

    Ok(quote!(
        impl #intro_generics
            // ::my_crate::PrometheusAnnotations for
            #StructName #fwd_generics
        #where_clause
        {
            #pub_ // <- remove if implementing trait rather than inherent function.
            fn prometheus_annotations ()
              -> &'static ::core::primitive::str
            {
                #prometheus_string
            }
        }
    ))
}

$ cargo run

error: there is no argument named `metric_type`
   --> into_map_derive\src\lib.rs:140:51
    |
140 |                 writeln!(prometheus_string, "TYPE {metric_type}").unwrap();
    |                                                   ^^^^^^^^^^^^^

error: there is no argument named `field_name`
   --> into_map_derive\src\lib.rs:157:42
    |
157 |             writeln!(prometheus_string, "{field_name} 4").unwrap();
    |                                          ^^^^^^^^^^^^

error[E0609]: no field `name` on type `syn::ItemStruct`
   --> into_map_derive\src\lib.rs:166:33
    |
166 |     let StructName @ _ = &input.name;
    |                                 ^^^^ unknown field
    |
    = note: available fields are: `attrs`, `vis`, `struct_token`, `ident`, `generics` ... and 2 others

any updates @Yandros

These are the exact same errors as with doc_string before, with the exact same fix.

According to documentation (which you should read anyway, if you're going to make any further changes), the field you need is input.ident.

2 Likes

Thanks for all help!!
I'm novice to macros and found even different from C.

trait PrometheusAnnotations cannot find the implementation in into_map_derive.
How can I convert Stats to String using trait prometheus_annotations?
main.rs

use into_map_derive::PrometheusAnnotations;
#[derive(PrometheusAnnotations)]
pub struct Stats {
    /// auth label
    #[metric_type("counter")]
    //#[serde(foo)]
    auth_errors: u64,
}

fn main() {
    let mut out = "".to_string();
    let a = Stats {
        auth_errors: 4,
    };
    out.push_str(
        a
    );

Cargo.toml

[package]
name = "into_map_demo"
version = "0.1.0"
edition = "2018"

[dependencies]
into_map = { path = "into_map" }
into_map_derive = { path = "into_map_derive" }

into_map\src\lib.rs

pub trait PrometheusAnnotations  {           //0 Implementations
    fn prometheus_annotations() -> &'static str;
}

into_map/Cargo.toml

[package]
name = "into_map"
version = "0.1.0"
edition = "2018"

into_map_derive/src/lib.rs

#![allow(nonstandard_style, unused_imports)]

use ::proc_macro::{
    TokenStream,
};

use ::proc_macro2::{
    Span,
    TokenStream as TokenStream2,
};
use ::quote::{
    format_ident,
    quote, quote_spanned,
    ToTokens,
};
use ::syn::{*,
    parse::{Parse, Parser, ParseStream},
    punctuated::Punctuated,
    spanned::Spanned,
    Result,
};

/// The `= "…"` in a `#[doc = "…"]` attribute,
/// _i.e._, the `…` in `/// …`.
struct DocString(String);

impl Parse for DocString {
    fn parse (input: ParseStream<'_>)
      -> Result<DocString>
    {
        let _: Token![=] = input.parse()?;
        let line: LitStr = input.parse()?;
        Ok(Self(line.value()))
    }
}

#[proc_macro_derive(PrometheusAnnotations, attributes(metric_type))] 
pub fn test (input: TokenStream)
  -> TokenStream
{
    prometheus_annotations(input.into())
        .unwrap_or_else(|err| err.to_compile_error())
        .into()
}

fn prometheus_annotations (
    input: TokenStream2,
) -> Result<TokenStream2>
{
    use ::core::fmt::Write;

    let input: ItemStruct = parse2(input)?;
    let mut prometheus_string = String::new();
    for field in &input.fields {
        let attrs = &field.attrs;
        let doc_strings =
            attrs.iter().filter_map(|attr| bool::then(
                attr.path.is_ident("doc"),
                || parse2::<DocString>(attr.tokens.clone()).ok(),
            ).flatten());
        
        doc_strings.for_each(|DocString(doc_string)| {
            writeln!(prometheus_string, "HELP {}", doc_string).unwrap();
            //prometheus_string = format!("HELP {}", doc_string);
            //writeln!(prometheus_string, "HELP auth label").unwrap();
        });

        let mut metric_types = attrs.iter().filter(|attr| attr.path.is_ident("metric_type"));
        match (metric_types.next(), metric_types.next()) {
            | (Some(attr), None) => {
                let metric_type: String = attr.parse_args::<LitStr>()?.value();
                //writeln!(prometheus_string, "TYPE {metric_type}").unwrap();
                prometheus_string = format!("TYPE {}", metric_type);
                prometheus_string += "\n";
                //writeln!(prometheus_string, "TYPE counter").unwrap();
            },
            | (None, None) => {
                // No `metric_type` provided: error or ignore?
                //…
            },
            | (Some(_), Some(duplicate)) => return Err(Error::new_spanned(
                // code to highlight / blame
                duplicate,
                // error message
                "Duplicate `metric_type` attribute",
            )),
            (None, Some(_)) => todo!(),
        };

        if let Some(ref field_name) = field.ident {
            //writeln!(prometheus_string, "{field_name} 4").unwrap();
            prometheus_string = format!("{} ", field_name);
            prometheus_string += "\n";
            //writeln!(prometheus_string, "auth_errors 4").unwrap();
        } else {
            return Err(Error::new(Span::call_site(), "expected a braced struct"));
        }
        
    }

    let (intro_generics, fwd_generics, where_clause) = input.generics.split_for_impl();
    let StructName @ _ = &input.ident;
    let pub_ = &input.vis;

    Ok(quote!(
        impl #intro_generics
            // ::my_crate::PrometheusAnnotations for
            #StructName #fwd_generics
        #where_clause
        {
            #pub_ // <- remove if implementing trait rather than inherent function.
            fn prometheus_annotations ()
              -> &'static ::core::primitive::str
            {
                #prometheus_string
            }
        }
    ))
}

into_map_derive/Cargo.toml

[package]
name = "into_map_derive"
version = "0.1.0"
edition = "2018"

[lib]
proc-macro = true

[dependencies]
proc-macro2.version = "1.0.37"
quote.version = "1.0.17"
syn.version = "1.0.91"
syn.features = ["full"]
into_map = { path="../into_map" }

$ cargo run

error[E0308]: mismatched types
  --> src\main.rs:43:9        
   |
43 |         a
   |         ^ expected `&str`, found struct `Stats`

We call function defined inside trait on structure, but pub trait PrometheusAnnotations shows 0 implementation.

    out.push_str(
        a.prometheus_annotations()       //no method named `prometheus_annotations` found for struct `Stats`
    );

So, I had commented out the trait stuff there, since I wasn't sure which approach you were going with, and also where your trait was gonna be located.

You should, thus, do:

    Ok(quote!(
        impl #intro_generics
-           // ::my_crate::PrometheusAnnotations for
+           ::into_map::PrometheusAnnotations for
            #StructName #fwd_generics
        #where_clause
        {
-           #pub_ // <- remove if implementing trait rather than inherent function.
  • (and feel free to reformat accordingly, I had used that formatting precisely because of git diffs: look at how neat they are :smile:)

Thanks @Yandros Still How to use prometheus_annotations() on struct stats variable.
Finally Need to convert struct stats to string.
For proc macros I used struct_variable.function()

#[derive(PrometheusAnnotations)]
pub struct Stats {
    /// auth label
    #[metric_type("counter")]
    //#[serde(foo)]
    auth_errors: u64,
}

fn main() {
    let mut out = "".to_string();
    let a = Stats {
        auth_errors: 4,
    };
    out.push_str(
        a
    );
}

$ cargo run

error[E0308]: mismatched types
  --> src\main.rs:44:9
   |
44 |         a
   |         ^ expected `&str`, found struct `Stats`

Even changing to prometheus_annotations_derive () does not help

#[proc_macro_derive(PrometheusAnnotations, attributes(metric_type))] 
pub fn prometheus_annotations_derive (input: TokenStream) -> TokenStream{
    pa(input.into())
        .unwrap_or_else(|err| err.to_compile_error())
        .into()
}
fn pa (
    input: TokenStream2,
) -> Result<TokenStream2>
{
      ..
}

You can't use Stats as &str. You can only convert it to &str via the derived method.

|| You can't use Stats as &str . You can only convert it to &str via the derived method
@Yandros @Cerber-Ursi That's correct

    let a = Stats {
        auth_errors: 4,
    };

But how to use derived method prometheus_annotations() on Stats so that its converted to &str?

pub trait PrometheusAnnotations  {
    fn prometheus_annotations() -> &'static str;
}

You've already used it before:

use into_map_derive::PrometheusAnnotations;

#[derive(PrometheusAnnotations)]
pub struct Stats {
    /// auth label
    #[metric_type("counter")]
    //#[serde(foo)]
    auth_errors: u64,
}

fn main() {
    let mut out = "".to_string();
    let a = Stats {
        auth_errors: 4,
    };

    out.push_str(
        a.prometheus_annotations()
    );
}

$ cargo run

error[E0599]: no method named `prometheus_annotations` found for struct `Stats` in the current scope
  --> src\main.rs:43:11
   |
14 | pub struct Stats {
   | ---------------- method `prometheus_annotations` not found for this
...
43 |         a.prometheus_annotations()
   |           ^^^^^^^^^^^^^^^^^^^^^^ method not found in `Stats`
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `prometheus_annotations`, perhaps you need to implement it:
           candidate #1: `PrometheusAnnotations`

@Cerber-Ursi @Yandros your thoughts here. How to apply prometheus_annotations() on Stats structure?

any updates @Yandros @Cerber-Ursi