Hone logo
Hone
Problems

Compile-Time Factorial Calculation in Rust

Compile-time computation allows you to perform calculations during compilation rather than at runtime, leading to performance improvements and potentially enabling more expressive code. This challenge asks you to implement a function that calculates the factorial of a non-negative integer at compile time using Rust's const generics and procedural macros.

Problem Description

You need to create a procedural macro factorial that expands to a constant value representing the factorial of a given non-negative integer. The macro should accept a single integer literal as input and produce a const expression that evaluates to the factorial of that integer. The macro should handle the base case (0! = 1) correctly. The macro should also be robust enough to handle relatively small integer inputs (see constraints).

Key Requirements:

  • Procedural Macro: The solution must be implemented as a procedural macro.
  • Compile-Time Evaluation: The factorial calculation must occur entirely at compile time.
  • const Expression: The macro must expand to a const expression.
  • Non-Negative Integer: The input must be a non-negative integer. While the macro itself doesn't need to enforce this (Rust's type system will handle it), the documentation should clearly state this requirement.
  • Error Handling: While full error handling isn't required, the macro should ideally not panic or produce undefined behavior if given an invalid input (e.g., a negative number). A compile-time error is preferable to a runtime panic.

Expected Behavior:

When the macro is used with a valid non-negative integer literal, it should expand to a const expression containing the factorial of that integer. When used with an invalid input (e.g., a negative number), the macro should ideally produce a compile-time error.

Examples

Example 1:

#[factorial(5)]
const FACTORIAL_5: u64 = 120;

fn main() {
    assert_eq!(FACTORIAL_5, 120);
}

Output: () Explanation: The macro expands #[factorial(5)] to const FACTORIAL_5: u64 = 120; because 5! = 120.

Example 2:

#[factorial(0)]
const FACTORIAL_0: u64 = 1;

fn main() {
    assert_eq!(FACTORIAL_0, 1);
}

Output: () Explanation: The macro expands #[factorial(0)] to const FACTORIAL_0: u64 = 1; because 0! = 1.

Example 3: (Edge Case - Larger Number)

#[factorial(3)]
const FACTORIAL_3: u64 = 6;

fn main() {
    assert_eq!(FACTORIAL_3, 6);
}

Output: () Explanation: The macro expands #[factorial(3)] to const FACTORIAL_3: u64 = 6; because 3! = 6.

Constraints

  • Input Range: The input integer must be in the range of 0 to 20 (inclusive). Factorials beyond this range can easily overflow a u64.
  • Output Type: The factorial will be stored as a u64.
  • Compile-Time: All calculations must be performed at compile time.
  • Macro Implementation: The solution must be a procedural macro.

Notes

  • Rust's const generics are crucial for this problem. You'll need to define a generic function that can be instantiated with a const parameter.
  • Consider using recursion within the macro to calculate the factorial.
  • The syn and quote crates are commonly used for procedural macro development. You'll need to add them to your Cargo.toml.
  • Think about how to handle the base case (0!) within your recursive implementation.
  • While explicit error handling for negative inputs isn't strictly required, a compile-time error is preferred over a runtime panic. You can achieve this by using conditional compilation or other techniques.
  • Remember that procedural macros are complex. Start with a simple implementation and gradually add complexity. Debugging can be challenging, so test thoroughly.
Loading editor...
rust