#async-stream #stream #async

async-fn-stream

Lightweight implementation of async-stream without macros

4 releases

new 0.2.2 Apr 23, 2024
0.2.1 Apr 18, 2024
0.2.0 Aug 3, 2022
0.1.0 Jul 7, 2022

#157 in Rust patterns

Download history 3397/week @ 2024-01-03 3906/week @ 2024-01-10 3750/week @ 2024-01-17 4138/week @ 2024-01-24 2455/week @ 2024-01-31 2778/week @ 2024-02-07 3739/week @ 2024-02-14 4111/week @ 2024-02-21 4479/week @ 2024-02-28 3609/week @ 2024-03-06 4207/week @ 2024-03-13 4391/week @ 2024-03-20 3009/week @ 2024-03-27 4431/week @ 2024-04-03 2645/week @ 2024-04-10 2770/week @ 2024-04-17

13,753 downloads per month
Used in 6 crates (5 directly)

MIT license

18KB
305 lines

A version of async-stream without macros. This crate provides generic implementations of Stream trait. Stream is an asynchronous version of std::iter::Iterator.

Two functions are provided - fn_stream and try_fn_stream.

Usage

Basic Usage

If you need to create a stream that may result in error, use try_fn_stream, otherwise use fn_stream.

To create a stream:

  1. Invoke fn_stream or try_fn_stream, passing a closure (anonymous function).
  2. Closure will accept an emitter. To return value from the stream, call .emit(value) on emitter and .await on its result. Once stream consumer has processed the value and called .next() on stream, .await will return.

Returning errors

try_fn_stream provides some conveniences for returning errors:

  1. Errors can be return from closure via return Err(...) or the question mark (?) operator. This will end the stream.
  2. An emitter also has an emit_err() method to return errors without ending the stream.

Examples

Finite stream of numbers

use async_fn_stream::fn_stream;
use futures_util::Stream;

fn build_stream() -> impl Stream<Item = i32> {
    fn_stream(|emitter| async move {
        for i in 0..3 {
            // yield elements from stream via `emitter`
            emitter.emit(i).await;
        }
    })
}

Read numbers from text file, with error handling

use anyhow::Context;
use async_fn_stream::try_fn_stream;
use futures_util::{pin_mut, Stream, StreamExt};
use tokio::{
    fs::File,
    io::{AsyncBufReadExt, BufReader},
};

fn read_numbers(file_name: String) -> impl Stream<Item = Result<i32, anyhow::Error>> {
    try_fn_stream(|emitter| async move {
        // Return errors via `?` operator.
        let file = BufReader::new(File::open(file_name).await.context("Failed to open file")?);
        pin_mut!(file);
        let mut line = String::new();
        loop {
            line.clear();
            let byte_count = file
                .read_line(&mut line)
                .await
                .context("Failed to read line")?;
            if byte_count == 0 {
                break;
            }

            for token in line.split_ascii_whitespace() {
                let Ok(number) = token.parse::<i32>() else {
                    // Return errors via the `emit_err` method.
                    emitter.emit_err(
                        anyhow::anyhow!("Failed to convert string \"{token}\" to number")
                    ).await;
                    continue;
                };
                emitter.emit(number).await;
            }
        }

        Ok(())
    })
}

Why not async-stream?

async-stream is great! It has a nice syntax, but it is based on macros which brings some flaws:

Dependencies

~760KB
~14K SLoC