Hone logo
Hone
Problems

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:

  1. Derive Macro: The solution must be a procedural derive macro.
  2. Debug Trait: The macro should implement std::fmt::Debug.
  3. Structs: For structs, the output should resemble StructName { field1: value1, field2: value2 }.
  4. Enums: For enums, the output should resemble EnumName::VariantName { field1: value1 } for structs, or EnumName::VariantName(value) for tuple variants, or EnumName::UnitVariant for unit variants.
  5. Field/Variant Names: The names of fields and variants must be included in the output.
  6. 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.rs of a procedural macro crate).
  • The generated Debug implementation must be efficient and correctly handle all standard Rust types that implement Debug.
  • You should not rely on external crates except for syn and quote for 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 syn to parse the input Rust code and quote to 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::Formatter object passed to the fmt method is crucial for writing output.
  • You'll need to implement the proc_macro::TokenStream to syn parsing and quote to proc_macro::TokenStream generation.
  • 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 Debug trait for various struct and enum scenarios as demonstrated in the examples, without any manual Debug implementation required on the user's part.
Loading editor...
rust