Procedural macros in Rust allow developers to extend the language and automate code generation. They are often used to create custom derive
attributes or other annotations that modify or add to Rust's functionality.
In this challenge, you'll implement a procedural macro to derive a custom trait called Describe
. The Describe
trait should provide a method, describe
, that returns a string containing the name of the struct and its fields with their values. This macro should generate an implementation of the trait automatically when applied to a struct.
Your task is to create a procedural macro named derive_describe
that can be used to generate the Describe
trait implementation for any struct. The generated describe
method should return a string representation of the struct, including its name and all its fields with their respective values.
For example, applying the macro to a struct like:
#[derive(Describe)]
struct Point {
x: i32,
y: i32,
}
Should generate a Describe
implementation such that:
let p = Point { x: 1, y: 2 };
assert_eq!(p.describe(), "Point { x: 1, y: 2 }");
The derive_describe
macro should:
Describe
trait implementation for the struct it is applied to.The describe
method should return a properly formatted string, including the struct name and fields with their values.
Procedural macros must be defined in a separate crate from the one where they are used. This is because procedural macros are compiled to a shared library that is loaded by the Rust compiler at compile time. The shared library must be compiled before the crate that uses the macro.
In your Cargo.toml
, you should define a new crate for the procedural macro:
[lib]
proc-macro = true
This is already done for this challenge, but it's important to understand the setup.
syn
crate to parse the input TokenStream
into an Abstract Syntax Tree (AST).quote
crate to generate Rust code as a TokenStream
.use proc_macro::TokenStream;use syn;use quote::quote;#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { println!("TokenStream! {}", input); let ast = syn::parse(input).unwrap(); impl_describe_macro(&ast)}fn impl_describe_macro(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; let fields = match &ast.data { syn::Data::Enum(_) => panic!("Cannot derive Describe for Enum"), syn::Data::Union(_) => panic!("Cannot derive Describe for Union"), syn::Data::Struct(data) => match &data.fields { syn::Fields::Named(fields) => fields.clone(), _ => return TokenStream::new(), }, }; // From solution: let field_names = fields.named.iter().map(|f| f.ident.as_ref().unwrap()); let field_names_as_strings = field_names.clone().map(|name| name.to_string()); let gen = quote! { impl Describe for #name { fn describe(&self) -> String{ let fields: Vec<String> = vec![ #(format!("{}: {:?}", #field_names_as_strings, self.#field_names)),* ]; format!("{} {{ {} }}", stringify!(#name), fields.join(", ")) } } }; gen.into()}
use proc_macro::TokenStream;use quote::quote;use syn::{parse_macro_input, DeriveInput, Data, Fields};// Powered by DeepSeek// Oops it's so clever than me...#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { // Parse the input tokens into a syntax tree let input = parse_macro_input!(input as DeriveInput); // Extract the struct name let name = &input.ident; // Extract the fields of the struct let fields = match &input.data { Data::Struct(data_struct) => match &data_struct.fields { Fields::Named(fields_named) => &fields_named.named, _ => panic!("Only structs with named fields are supported"), }, _ => panic!("Only structs are supported"), }; // Generate the code to format each field let field_descriptions = fields.iter().map(|field| { let field_name = &field.ident; quote! { format!("{}: {:?}", stringify!(#field_name), self.#field_name) } }); // Generate the implementation of the Describe trait let expanded = quote! { impl Describe for #name { fn describe(&self) -> String { let field_descriptions: Vec<String> = vec![#(#field_descriptions),*]; format!("{} {{ {} }}", stringify!(#name), field_descriptions.join(", ")) } } }; // Return the generated implementation as a TokenStream TokenStream::from(expanded) }// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use proc_macro::TokenStream;use quote::quote;use syn::{parse_macro_input, DeriveInput, Data, Fields};// Powered by DeepSeek// Oops it's so clever than me...#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { // Parse the input tokens into a syntax tree let input = parse_macro_input!(input as DeriveInput); // Extract the struct name let name = &input.ident; // Extract the fields of the struct let fields = match &input.data { Data::Struct(data_struct) => match &data_struct.fields { Fields::Named(fields_named) => &fields_named.named, _ => panic!("Only structs with named fields are supported"), }, _ => panic!("Only structs are supported"), }; // Generate the code to format each field let field_descriptions = fields.iter().map(|field| { let field_name = &field.ident; quote! { format!("{}: {:?}", stringify!(#field_name), self.#field_name) } }); // Generate the implementation of the Describe trait let expanded = quote! { impl Describe for #name { fn describe(&self) -> String { let field_descriptions: Vec<String> = vec![#(#field_descriptions),*]; format!("{} {{ {} }}", stringify!(#name), field_descriptions.join(", ")) } } }; // Return the generated implementation as a TokenStream TokenStream::from(expanded) }// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use proc_macro::TokenStream;use quote::quote;use syn::{parse_macro_input,DeriveInput};#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { // TODO: Implement the procedural macro here let input: DeriveInput = parse_macro_input!(input); let name = &input.ident; let data = &input.data; let named_fields = match data { syn::Data::Struct(s) => { match &s.fields { syn::Fields::Named(named) => { &named.named }, _ => { panic!("Only named fields are supported"); } } }, _ => { panic!("#[derive(Describe)] only works on structs"); }, }; let field = named_fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_name = field.clone().map(|f| { f.to_string()}); TokenStream::from(quote! { impl Describe for #name { fn describe(&self) -> String { let data_fields = vec![#(format!("{}: {:?}",#field_name, self.#field) ), *]; format!("{} {} {} {}",stringify!(#name), "{", data_fields.join(", "),"}") } } })}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use proc_macro::TokenStream;use quote::quote;use syn::{parse_macro_input,DeriveInput};#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { // TODO: Implement the procedural macro here let input: DeriveInput = parse_macro_input!(input); let name = &input.ident; let data = &input.data; let named_fields = match data { syn::Data::Struct(s) => { match &s.fields { syn::Fields::Named(named) => { &named.named }, _ => { panic!("Only named fields are supported"); } } }, _ => { panic!("#[derive(Describe)] only works on structs"); }, }; let field = named_fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_name = field.clone().map(|f| { f.to_string()}); TokenStream::from(quote! { impl Describe for #name { fn describe(&self) -> String { let data_fields = vec![#(format!("{}: {:?}",#field_name, self.#field) ), *]; format!("{} {} {} {}",stringify!(#name), "{", data_fields.join(", "),"}") } } })}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use quote::quote;use proc_macro::TokenStream;use syn::{parse_macro_input, Data, DeriveInput};use syn::Fields::Named;#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { let ast: DeriveInput = parse_macro_input!(input); let name = &ast.ident; let fields = match &ast.data { Data::Struct(data_struct) => { if let Named(named_fields) = &data_struct.fields { &named_fields.named } else { return TokenStream::new() } } _ => return TokenStream::new() }; let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_names_str = field_names.clone().map(|f| f.to_string()); let expanded = quote! { impl Describe for #name { fn describe(&self) -> String { let fields: Vec<String> = vec![ #(format!("{}: {:?}", #field_names_str, self.#field_names)),* ]; format!("{} {{ {} }}", stringify!(#name), fields.join(", ")) } } }; expanded.into()}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
extern crate proc_macro;use proc_macro::TokenStream;use quote::quote;use syn::{parse_macro_input, Data, DeriveInput};#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); let name = ast.ident; let data: Data = ast.data; let fields = match &data { Data::Struct(data_struct) => { if let syn::Fields::Named(named_fields) = &data_struct.fields { &named_fields.named } else { panic!("Not implemented"); } } _ => panic!("Not implemented"), }; let field_names = fields .into_iter() .map(|field| field.ident.as_ref().unwrap()); let field_names_str = field_names.clone().map(|name| name.to_string()); let expanded = quote! { impl Describe for #name { fn describe(&self) -> String { let fields: Vec<String> = vec![ #(format!("{}: {:?}", #field_names_str, self.#field_names)),* ]; format!("{} {{ {} }}", stringify!(#name), fields.join(", ")) } } }; TokenStream::from(expanded)}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use quote::quote;use proc_macro::TokenStream;use syn::{parse_macro_input, Data, DeriveInput};use syn::Fields::Named;#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { let ast: DeriveInput = parse_macro_input!(input); let name = &ast.ident; let fields = match &ast.data { Data::Struct(data_struct) => { if let Named(named_fields) = &data_struct.fields { &named_fields.named } else { return TokenStream::new() } } _ => return TokenStream::new() }; let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_names_str = field_names.clone().map(|f| f.to_string()); let expanded = quote! { impl Describe for #name { fn describe(&self) -> String { let fields: Vec<String> = vec![ #(format!("{}: {:?}", #field_names_str, self.#field_names)),* ]; format!("{} {{ {} }}", stringify!(#name), fields.join(", ")) } } }; expanded.into()}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use quote::quote;use proc_macro::TokenStream;use syn::{parse_macro_input, Data, DeriveInput};use syn::Fields::Named;#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { let ast: DeriveInput = parse_macro_input!(input); let name = &ast.ident; let fields = match &ast.data { Data::Struct(data_struct) => { if let Named(named_fields) = &data_struct.fields { &named_fields.named } else { return TokenStream::new() } } _ => return TokenStream::new() }; let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_names_str = field_names.clone().map(|f| f.to_string()); let expanded = quote! { impl Describe for #name { fn describe(&self) -> String { let fields: Vec<String> = vec![ #(format!("{}: {:?}", #field_names_str, self.#field_names)),* ]; format!("{} {{ {} }}", stringify!(#name), fields.join(", ")) } } }; expanded.into()}
use proc_macro::TokenStream;use quote::quote;use syn::Fields::Named;use syn::{parse_macro_input, Data, DeriveInput};#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { let ast: DeriveInput = parse_macro_input!(input); let name = &ast.ident; let fields = match &ast.data { Data::Struct(data_struct) => { if let Named(named_fields) = &data_struct.fields { &named_fields.named } else { return TokenStream::new() } } _ => return TokenStream::new() }; let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_names_str = field_names.clone().map(|s| s.to_string()); let expanded = quote! { impl Describe for #name { fn describe(&self) -> String { let fields: Vec<String> = vec![ #(format!("{}: {:?}", #field_names_str, self.#field_names)),* ]; format!("{} {{ {} }}", stringify!(#name), fields.join(", ")) } } }; return TokenStream::from(expanded);}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use proc_macro::TokenStream;use quote::quote;use syn::Fields::Named;use syn::{parse_macro_input, Data, DeriveInput};#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { let ast: DeriveInput = parse_macro_input!(input); let name = &ast.ident; let fields = match &ast.data { Data::Struct(data_struct) => { if let Named(named_fields) = &data_struct.fields { &named_fields.named } else { return TokenStream::new(); } } _ => return TokenStream::new(), }; let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_names_str = field_names.clone().map(|n| n.to_string()); let expanded = quote! { impl Describe for #name { fn describe(&self) -> String { let fields: Vec<String> = vec! [ #(format!("{}: {:?}", #field_names_str, self.#field_names)),* ]; format!("{} {{ {} }}", stringify!(#name), fields.join(", ")) } } }; TokenStream::from(expanded)}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use proc_macro::TokenStream;use syn::{parse_macro_input, DeriveInput, Fields, FieldsNamed};#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let struct_name = input.ident; let syn::Data::Struct(data) = input.data else { return TokenStream::new(); }; let Fields::Named(FieldsNamed { named, .. }) = &data.fields else { return TokenStream::new(); }; let field_names = named.iter().filter_map(|field| field.ident.as_ref()); let expanded = quote::quote! { impl Describe for #struct_name { fn describe(&self) -> String { let fields: Vec<String> = vec![ #(format!("{}: {:?}", stringify!(#field_names), self.#field_names)),* ]; format!("{} {{ {} }}", stringify!(#struct_name), fields.join(", ")) } } }; TokenStream::from(expanded)}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use proc_macro::TokenStream;use quote::quote;use syn::{parse_macro_input, DeriveInput};#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { // TODO: Implement the procedural macro here let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; let fields = match &input.data { syn::Data::Struct(s) => { if let syn::Fields::Named(named_fields) = &s.fields { &named_fields.named } else { return TokenStream::new(); // Ignore unnamed fields } }, _ => { return TokenStream::new(); } // Ignore other types [enum, union] }; let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_names_str = field_names.clone().map(|n| n.to_string()); let expanded = quote!{ impl Describe for #name { fn describe(&self) -> String { let fields: Vec<String> = vec! [ #(format!("{}: {:?}", #field_names_str, self.#field_names)),* ]; format!("{} {{ {} }}", stringify!(#name), fields.join(", ")) } } }; TokenStream::from(expanded)}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use proc_macro::TokenStream;use syn;use quote::quote;#[proc_macro_derive(Describe)]pub fn describe_derive(input: TokenStream) -> TokenStream { // Parse input token stream. let ast = syn::parse_macro_input!(input as syn::DeriveInput); let name = &ast.ident; // Build a list of fields. let fields = match &ast.data { syn::Data::Struct(s) => { if let syn::Fields::Named(named_fields) = &s.fields { &named_fields.named } else { return TokenStream::new(); // Ignore unnamed fields. } }, _ => { return TokenStream::new(); }, // Ignore enums and unions. }; let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_names_str = field_names.clone().map(|name| name.to_string()); // Generate output token stream. let expanded = quote! { impl Describe for #name { fn describe(&self) -> String { let fields: Vec<String> = vec![ #(format!("{}: {:?}", #field_names_str, self.#field_names)),* ]; format!("{} {{ {} }}", stringify!(#name), fields.join(", ")) } } }; TokenStream::from(expanded)}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use quote::quote;use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, FieldsNamed};use proc_macro::TokenStream;#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; let fields = if let Data::Struct(DataStruct { fields: Fields::Named(FieldsNamed { ref named, .. }), .. }) = input.data { named } else { return TokenStream::new(); }; let field_idents = fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_names = field_idents.clone().map(|name| name.to_string()); quote! { impl #name { fn describe(&self) -> String { let fields: Vec<String> = vec![ #(format!("{}: {:?}", #field_names, self.#field_idents)),* ]; format!("{} {{ {} }}", stringify!(#name), fields.join(", ")) } } } .into()}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use proc_macro::TokenStream;use quote::quote;use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, FieldsNamed};#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { // TODO: Implement the procedural macro here let ast = parse_macro_input!(input as DeriveInput); let name = &ast.ident; let fields = if let Data::Struct(DataStruct { fields: Fields::Named(FieldsNamed { ref named, .. }), .. }) = ast.data { named } else { return TokenStream::new(); }; let attrs = fields .iter() .map(|it| it.ident.as_ref().unwrap()) .collect::<Vec<_>>(); let attr_names = attrs.iter().map(|it| it.to_string()).collect::<Vec<_>>(); quote! { impl #name { pub fn describe(&self) -> String { let attr_list = vec![ #(format!("{}: {:?}", #attr_names, self.#attrs)),* ]; format!("{} {{ {} }}", stringify!(#name), attr_list.join(", ")) } } } .into()}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}
use proc_macro::TokenStream;use quote::quote;use syn::{parse_macro_input, Data, DeriveInput, Fields};#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let struct_name = input.ident; // Only proceed if the struct has named fields let fields = if let Data::Struct(data_struct) = input.data { if let Fields::Named(named_fields) = data_struct.fields { named_fields.named } else { return TokenStream::new(); // Ignore non-named fields } } else { return TokenStream::new(); // Ignore non-struct data }; let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_names_as_strings = field_names.clone().map(|name| name.to_string()); let generated = quote! { impl Describe for #struct_name { fn describe(&self) -> String { let fields: Vec<String> = vec![ #(format!("{}: {:?}", #field_names_as_strings, self.#field_names)),* ]; format!("{} {{ {} }}", stringify!(#struct_name), fields.join(", ")) } } }; generated.into()}
use proc_macro::TokenStream;use quote::quote;use syn::{parse_macro_input, Data, DeriveInput, Fields};#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let struct_name = input.ident; // Only proceed if the struct has named fields let fields = if let Data::Struct(data_struct) = input.data { if let Fields::Named(named_fields) = data_struct.fields { named_fields.named } else { return TokenStream::new(); // Ignore non-named fields } } else { return TokenStream::new(); // Ignore non-struct data }; let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_names_as_strings = field_names.clone().map(|name| name.to_string()); let generated = quote! { impl Describe for #struct_name { fn describe(&self) -> String { let fields: Vec<String> = vec![ #(format!("{}: {:?}", #field_names_as_strings, self.#field_names)),* ]; format!("{} {{ {} }}", stringify!(#struct_name), fields.join(", ")) } } }; generated.into()}// Example Test//#[test]//fn test_example() {// #[derive(Describe)]// struct Person {// name: String,// age: u32,// }//// let person = Person {// name: "Alice".to_string(),// age: 30,// };//// assert_eq!(person.describe(), "Person { name: \"Alice\", age: 30 }");//}