Implementing a Bounded Async Range Iterator in Rust
Asynchronous programming in Rust is becoming increasingly important for building performant and responsive applications. A fundamental building block for asynchronous iteration is the AsyncIterator trait. This challenge will guide you through implementing a custom asynchronous iterator that generates a sequence of numbers within a specified bound, mimicking the behavior of a synchronous range but operating asynchronously.
Problem Description
Your task is to create an AsyncRange struct that implements the AsyncIterator trait for u32 values. This iterator should generate numbers starting from a given start value up to (but not including) an end value. The iteration should be asynchronous, meaning that each next call might involve waiting for some simulated asynchronous work.
Key Requirements:
- The
AsyncRangestruct should havestartandendfields of typeu32. - It must implement the
futures::stream::Streamtrait, specifically requiring theItemto beu32. - The
poll_nextmethod should returnPoll::Ready(Some(value))when a new number is available. - The
poll_nextmethod should returnPoll::Ready(None)when the iteration is complete (i.e., when the current value reachesend). - To simulate asynchronous work, introduce a small, configurable delay before yielding each number. This delay can be simulated using
tokio::time::sleep.
Expected Behavior:
When an AsyncRange is iterated over using an async for loop, it should yield numbers from start up to end - 1.
Important Edge Cases:
start >= end: If thestartvalue is greater than or equal to theendvalue, the iterator should immediately yieldNonewithout producing any numbers.- Large Ranges: Consider how your implementation would handle very large ranges, though for this challenge, typical usage is expected.
Examples
Example 1:
use futures::stream::StreamExt;
use tokio;
// Assume AsyncRange is defined and implemented correctly
#[tokio::main]
async fn main() {
let mut range = AsyncRange::new(0, 5, std::time::Duration::from_millis(10));
let mut collected = Vec::new();
while let Some(val) = range.next().await {
collected.push(val);
}
println!("{:?}", collected);
}
Output:
[0, 1, 2, 3, 4]
Explanation:
The AsyncRange starts at 0 and goes up to 5 (exclusive). Each number is yielded after a small delay. The async for loop collects these numbers into a vector.
Example 2:
use futures::stream::StreamExt;
use tokio;
// Assume AsyncRange is defined and implemented correctly
#[tokio::main]
async fn main() {
let mut range = AsyncRange::new(10, 10, std::time::Duration::from_millis(10));
let mut count = 0;
while let Some(_) = range.next().await {
count += 1;
}
println!("Count: {}", count);
}
Output:
Count: 0
Explanation:
When start is equal to end, the range is empty, and the iterator immediately signals completion.
Example 3:
use futures::stream::StreamExt;
use tokio;
// Assume AsyncRange is defined and implemented correctly
#[tokio::main]
async fn main() {
let mut range = AsyncRange::new(7, 5, std::time::Duration::from_millis(10));
let mut count = 0;
while let Some(_) = range.next().await {
count += 1;
}
println!("Count: {}", count);
}
Output:
Count: 0
Explanation:
When start is greater than end, the range is also considered empty, and the iterator completes without yielding any values.
Constraints
- The
startandendvalues will beu32. - The
delaywill be astd::time::Duration. - The solution must be built using Rust and the
tokioruntime. - You will need to add
futuresandtokioas dependencies to yourCargo.toml. - The
AsyncRangestruct should manage its internal state correctly to ensure proper iteration.
Notes
- You'll need to import
futures::stream::Streamandfutures::task::{Context, Poll}. - The
StreamExttrait provides the convenient.next().awaitmethod. - For simulating the delay, use
tokio::time::sleep(self.delay).await;within your asynchronous logic. However, remember thatpoll_nextshould notawaitdirectly. Instead, you'll need to manage the state to know when the sleep is complete. A common pattern for this is to usetokio::time::sleepand atokio::pinto manage the future. - Consider how to manage the internal state of the iterator (the current number being yielded) and the ongoing asynchronous operation (the sleep future).
- When
poll_nextis called, and the asynchronous operation (sleep) is not yet complete, it should returnPoll::Pending. You will need to ensure that the future is correctly pinned and that theContextis used to re-poll when the operation completes.