Declarative Macro for Generic Vector Operations
This challenge focuses on creating a declarative macro in Rust that can perform common operations on vectors of different types. This is useful for reducing boilerplate code when you need to apply the same logic to various data types without sacrificing type safety.
Problem Description
Your task is to create a declarative macro named apply_vec_op that accepts three arguments:
- A vector (
Vec<T>). - A string representing the operation to perform ("sum", "product", "average").
- A type hint for the result of the operation.
The macro should dynamically perform the specified operation on the elements of the input vector.
Key Requirements:
- The macro must handle vectors of numeric types (e.g.,
i32,f64). - It should support the following operations:
"sum": Calculates the sum of all elements."product": Calculates the product of all elements."average": Calculates the average of all elements.
- The macro should return a value of the type specified by the type hint.
- The macro should gracefully handle an empty input vector for "sum", "product", and "average" operations.
- For "sum" and "average" on an empty vector, it should return the additive identity (0 for numeric types).
- For "product" on an empty vector, it should return the multiplicative identity (1 for numeric types).
- The macro should panic with a descriptive error message if an unsupported operation string is provided.
Expected Behavior:
The apply_vec_op macro should expand to code that efficiently computes the requested operation.
Examples
Example 1:
let numbers = vec![1, 2, 3, 4, 5];
let sum: i32 = apply_vec_op!(numbers, "sum", i32);
// sum should be 15
Explanation: The macro expands to calculate the sum of the numbers vector, returning an i32.
Example 2:
let floats = vec![1.1, 2.2, 3.3];
let average: f64 = apply_vec_op!(floats, "average", f64);
// average should be approximately 2.2
Explanation: The macro calculates the average of the floats vector, returning an f64.
Example 3:
let values = vec![2, 3, 4];
let product: i64 = apply_vec_op!(values, "product", i64);
// product should be 24
Explanation: The macro calculates the product of the values vector, returning an i64.
Example 4 (Edge Case - Empty Vector):
let empty_vec: Vec<f32> = vec![];
let sum_of_empty: f32 = apply_vec_op!(empty_vec, "sum", f32);
// sum_of_empty should be 0.0
Explanation: For an empty vector and the "sum" operation, the macro should return the additive identity.
Example 5 (Edge Case - Unsupported Operation):
let data = vec![10, 20];
// This line should panic:
// let result = apply_vec_op!(data, "max", i32);
Explanation: Providing an unsupported operation string like "max" should cause the macro to panic with an error message indicating the invalid operation.
Constraints
- The input vector will always contain elements that can be summed, multiplied, or averaged (e.g., numeric types that implement
Add,Mul,Div, andFrom<i32>for initial values andDefaultfor an identity). - The operation string will be a string literal (
"sum","product","average"). - The type hint will be a valid Rust type that can represent the result.
- The macro should not require the caller to specify the element type of the vector if it can be inferred. However, the type hint for the result is mandatory.
- For "average", the calculation should handle potential floating-point division.
Notes
- Consider how to iterate over the vector and accumulate results.
- Think about how to conditionally execute different code paths based on the operation string.
- Rust's
matchstatement is a powerful tool for handling different cases. - Remember to use
stringify!or similar techniques if you need to refer to the operation name as a string within the macro. - For the "average" operation, be mindful of integer division if the input vector contains integers and the desired output is also an integer. The problem statement implies the result type is specified, so ensure the calculation is compatible.
- The
macro_rules!syntax is key to creating declarative macros.