Hone logo
Hone
Problems

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 AsyncRange struct should have start and end fields of type u32.
  • It must implement the futures::stream::Stream trait, specifically requiring the Item to be u32.
  • The poll_next method should return Poll::Ready(Some(value)) when a new number is available.
  • The poll_next method should return Poll::Ready(None) when the iteration is complete (i.e., when the current value reaches end).
  • 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 the start value is greater than or equal to the end value, the iterator should immediately yield None without 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 start and end values will be u32.
  • The delay will be a std::time::Duration.
  • The solution must be built using Rust and the tokio runtime.
  • You will need to add futures and tokio as dependencies to your Cargo.toml.
  • The AsyncRange struct should manage its internal state correctly to ensure proper iteration.

Notes

  • You'll need to import futures::stream::Stream and futures::task::{Context, Poll}.
  • The StreamExt trait provides the convenient .next().await method.
  • For simulating the delay, use tokio::time::sleep(self.delay).await; within your asynchronous logic. However, remember that poll_next should not await directly. Instead, you'll need to manage the state to know when the sleep is complete. A common pattern for this is to use tokio::time::sleep and a tokio::pin to 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_next is called, and the asynchronous operation (sleep) is not yet complete, it should return Poll::Pending. You will need to ensure that the future is correctly pinned and that the Context is used to re-poll when the operation completes.
Loading editor...
rust