Hone logo
Hone
Problems

Implementing a Custom Attribute Macro in Rust

Attribute macros in Rust allow you to modify or add behavior to items like functions, structs, modules, and more. This challenge asks you to implement a custom attribute macro that automatically generates a debug print statement for a given function, logging its name and arguments when it's called. This is a common pattern for debugging and can be automated with macros.

Problem Description

You need to create a Rust attribute macro named debug_log that, when applied to a function, will wrap the function's execution with code that prints a debug message to the console. The debug message should include the function's name and its arguments. The original function's logic should still be executed as normal.

Key Requirements:

  • The macro must be a function-level attribute.
  • The macro should print a debug message to std::io::stdout before the function executes.
  • The debug message should include the function's name and a string representation of its arguments.
  • The original function should be executed without modification to its core logic.
  • The macro should handle functions with any number of arguments and any argument types.

Expected Behavior:

When a function is decorated with the #[debug_log] attribute, calling that function will first print a debug message to the console, followed by the function's original return value.

Edge Cases to Consider:

  • Functions with no arguments.
  • Functions with complex argument types (structs, enums, etc.).
  • Functions returning different types.
  • Error handling within the original function (the debug log should still execute even if the function panics).

Examples

Example 1:

#[debug_log]
fn add(x: i32, y: i32) -> i32 {
    x + y
}

fn main() {
    let result = add(5, 3);
    println!("Result: {}", result);
}

Output:

Debug: add(x: 5, y: 3)
Result: 8

Explanation: The add function is decorated with #[debug_log]. When add(5, 3) is called, the macro first prints "Debug: add(x: 5, y: 3)" to the console, then executes the function, and finally prints "Result: 8".

Example 2:

#[debug_log]
fn greet(name: &str) {
    println!("Hello, {}!", name);
}

fn main() {
    greet("Alice");
}

Output:

Debug: greet(name: "Alice")
Hello, Alice!

Explanation: The greet function is decorated with #[debug_log]. The macro prints "Debug: greet(name: "Alice")" before printing "Hello, Alice!".

Example 3:

#[debug_log]
fn no_args() {
    println!("No arguments here!");
}

fn main() {
    no_args();
}

Output:

Debug: no_args()
No arguments here!

Explanation: The no_args function has no arguments. The macro correctly prints "Debug: no_args()" before executing the function.

Constraints

  • The macro must be implemented using procedural macros.
  • The debug message must be printed to std::io::stdout.
  • The macro should be compatible with functions that return any type.
  • The macro should not significantly impact the performance of the original function (avoid unnecessary allocations or complex operations within the macro itself).
  • The macro should compile and run without errors.

Notes

  • You'll need to create a new Rust crate with the proc-macro feature enabled.
  • Consider using the quote! macro from the proc-macro2 crate to generate the code that wraps the function.
  • The TokenStream type represents a sequence of tokens. You'll need to parse the function's signature from the TokenStream to extract its name and arguments.
  • The syn crate can be helpful for parsing Rust code into a structured representation.
  • Remember to handle potential errors gracefully during parsing and code generation. A panic within the macro can lead to difficult-to-debug issues.
Loading editor...
rust