Generic Data Processor in Rust
This challenge focuses on creating a generic function in Rust that can operate on different types of data. You'll learn how to leverage Rust's generics and trait bounds to write flexible and reusable code that adapts to various data structures and operations. This is a fundamental concept for building robust and scalable Rust applications.
Problem Description
Your task is to implement a generic function named process_data that takes a collection of items and a processing function as input. The process_data function should iterate over each item in the collection, apply the provided processing function to it, and return a new collection containing the results.
Key Requirements:
- The
process_datafunction must be generic over the type of items in the input collection and the type of the output from the processing function. - The function should accept any type that can be iterated over (e.g.,
Vec,Slice, arrays). - The processing function should accept a reference to an item from the collection and return a value of the output type.
- The
process_datafunction should return aVecof the processed items.
Expected Behavior:
For each element in the input collection, the process_data function will call the provided processor function, passing a reference to the current element. The return value of the processor function will be collected into a new Vec.
Edge Cases to Consider:
- An empty input collection.
- Input collections containing different primitive types.
- Input collections containing custom struct types.
Examples
Example 1:
Input:
data = vec![1, 2, 3, 4, 5]
processor = |x: &i32| x * 2
Output:
vec![2, 4, 6, 8, 10]
Explanation: The `process_data` function iterates through the `data` vector. For each number, it doubles it using the provided closure, and collects the results into a new vector.
Example 2:
Input:
data = vec!["hello", "world", "rust"]
processor = |s: &&str| s.to_uppercase()
Output:
vec!["HELLO", "WORLD", "RUST"]
Explanation: The function processes a vector of string slices. The processor converts each string slice to its uppercase equivalent. Note the double reference `&&str` because the iterator yields references to the elements, which are themselves `&str`.
Example 3:
Input:
struct Point { x: i32, y: i32 }
data = vec![Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]
processor = |p: &Point| p.x + p.y
Output:
vec![3, 7]
Explanation: This example demonstrates processing a vector of custom structs. The processor calculates the sum of the `x` and `y` fields for each `Point`.
Constraints
- The input collection should be borrowable as an iterator.
- The processing function should return a type that can be stored in a
Vec. - The generic type parameters should be sufficiently constrained to ensure type safety.
- Performance is not a primary concern for this challenge; focus on correctness and idiomatic Rust.
Notes
Consider using Rust's Iterator trait and its associated methods like map and collect to simplify the implementation. You'll need to think about how to express the constraints on the input collection type and the processing function using trait bounds. Specifically, you might need to constrain the input collection type to implement IntoIterator.