Rust Derive Macro: Automatic Debug Implementation
This challenge asks you to create a custom derive macro in Rust that automatically implements the Debug trait for a given struct or enum. This is a fundamental exercise in understanding Rust's procedural macros and how they can be used to reduce boilerplate code.
Problem Description
You need to create a procedural derive macro named AutoDebug. When this macro is applied to a struct or enum, it should generate an implementation of the std::fmt::Debug trait. The generated fmt method should print the name of the type and then, for each field (in structs) or variant (in enums), it should print the field/variant name and its corresponding value, enclosed in curly braces.
Key Requirements:
- Derive Macro: The solution must be a procedural derive macro.
DebugTrait: The macro should implementstd::fmt::Debug.- Structs: For structs, the output should resemble
StructName { field1: value1, field2: value2 }. - Enums: For enums, the output should resemble
EnumName::VariantName { field1: value1 }for structs, orEnumName::VariantName(value)for tuple variants, orEnumName::UnitVariantfor unit variants. - Field/Variant Names: The names of fields and variants must be included in the output.
- Nested Types: The macro should correctly handle fields and variants of types that also implement
Debug.
Expected Behavior:
The macro should automatically generate code that, when invoked (e.g., via println!("{:?}", instance)), produces output consistent with the standard library's Debug formatting for similar data structures.
Edge Cases:
- Structs with no fields.
- Enums with unit variants.
- Enums with tuple variants.
- Enums with struct variants.
- Enums with a mix of variant types.
- Fields/variants that are themselves complex types (e.g., other structs, enums, collections).
Examples
Example 1: Simple Struct
use auto_debug::AutoDebug; // Assuming your macro is in a crate named 'auto_debug'
#[derive(AutoDebug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 10, y: 20 };
println!("{:?}", p);
}
Expected Output:
Point { x: 10, y: 20 }
Explanation: The macro generates a Debug implementation that prints the struct name "Point" followed by its fields "x" and "y" with their respective values.
Example 2: Enum with Multiple Variants
use auto_debug::AutoDebug;
#[derive(AutoDebug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg1 = Message::Quit;
let msg2 = Message::Move { x: 1, y: 2 };
let msg3 = Message::Write("Hello".to_string());
let msg4 = Message::ChangeColor(0, 100, 255);
println!("{:?}", msg1);
println!("{:?}", msg2);
println!("{:?}", msg3);
println!("{:?}", msg4);
}
Expected Output:
Message::Quit
Message::Move { x: 1, y: 2 }
Message::Write("Hello")
Message::ChangeColor(0, 100, 255)
Explanation: The macro correctly formats each variant, including its name and data. For Move, it uses struct-like formatting. For Write and ChangeColor, it uses tuple-like formatting. For Quit, it's a unit variant.
Example 3: Nested Struct
use auto_debug::AutoDebug;
#[derive(AutoDebug)]
struct Outer {
data: Inner,
id: u64,
}
#[derive(AutoDebug)]
struct Inner {
value: String,
}
fn main() {
let inner_data = Inner { value: "test".to_string() };
let outer_data = Outer { data: inner_data, id: 12345 };
println!("{:?}", outer_data);
}
Expected Output:
Outer { data: Inner { value: "test" }, id: 12345 }
Explanation: The macro recursively calls the Debug implementation for nested structs, ensuring proper formatting.
Constraints
- Your derive macro must be implemented using
proc_macro. - The macro should be placed in a separate crate (e.g.,
lib.rsof a procedural macro crate). - The generated
Debugimplementation must be efficient and correctly handle all standard Rust types that implementDebug. - You should not rely on external crates except for
synandquotefor parsing and generating Rust code.
Notes
- This challenge is designed to teach you about procedural macros in Rust. You'll need to use crates like
synto parse the input Rust code andquoteto generate the output Rust code. - Consider how to represent the generated code using
quote. - Pay close attention to the different kinds of fields in structs (named, tuple) and variants in enums (unit, tuple, struct).
- The
std::fmt::Formatterobject passed to thefmtmethod is crucial for writing output. - You'll need to implement the
proc_macro::TokenStreamtosynparsing andquotetoproc_macro::TokenStreamgeneration. - Think about how to handle type parameters if your macro were to be applied to generic structs/enums (though for this challenge, focusing on non-generic types is sufficient unless you want an extra challenge).
- Success is defined by correctly implementing the
Debugtrait for various struct and enum scenarios as demonstrated in the examples, without any manualDebugimplementation required on the user's part.