futures-lite

repository·master·Indexed 20 days ago

https://github.com/smol-rs/futures-lite

A lightweight, fast-compiling async prelude providing a subset of the futures crate with improved ergonomics and minimal unsafe code. It includes combinators for futures and streams, async I/O utilities like BufReader and BufWriter, and tools for bridging synchronous and asynchronous code such as block_on. Version 2.6.1.

Tokens
12.9K
Snippets
52
Records
58
Agent score
64%

What's inside futures-lite

  1. Overview of futures-lite

    master
    futures-lite is a lightweight async prelude designed as a subset of the futures crate. It is optimized for faster compilation, improved API ergonomics, and reduced use of unsafe code. It is intended to be fully compatible with the futures crate while maintaining an intentionally constrained API surface to keep the dependency footprint small.
  2. Handling concurrency with race and zip

    master

    While futures-lite does not provide high-order concurrency primitives like FuturesUnordered or for_each_concurrent to keep complexity low, it provides race and zip for handling two futures at once.

    For a fixed number of futures: Use the futures-concurrency crate to join or race a specific number of futures.

    use futures_concurrency::prelude::*;
    let (x, y, z) = (a, b, c).join().await;

    For a variable number of futures: It is recommended to use an executor like smol (which provides Executor and LocalExecutor) rather than trying to manage large sets of futures with combinators.

    Implementing select! logic: You can implement select! behavior using async/await and the race combinator:

    let x = ( 
        async move { a.await + 1 },
        async move { b.await; 0 },
        async move { c.await + 3 }
    ).race().await;
    let x = ( 
        async move { a.await + 1 },
        async move { b.await; 0 },
        async move { c.await + 3 }
    ).race().await;
  3. Why Sink trait is not supported

    master

    futures-lite intentionally does not support the Sink trait. The Sink API is considered overly complex due to its Error subtype and multi-call requirements, which necessitate internal buffering.

    Instead of using Sink, users should look for APIs that provide an async fn send() method. If a crate only exposes a Sink implementation, it may be difficult to use within the futures-lite ecosystem.

  4. How asynchronous closures work in futures-lite

    master

    Most combinators in futures-lite take regular closures rather than async closures. This simplifies implementation and avoids the need to wrap trivial values in async move { ... } or future::ready(...).

    For Streams: If you need to perform an asynchronous operation within a stream combinator (like all), use the .then() combinator to transform the stream into one that yields the results of your async function, then apply your logic.

    Example: Using async logic in a stream Instead of trying to pass an async closure to .all():

    // In futures-lite, use `then` and pass the result to `all`.
    my_stream.then(|x| my_async_fn(x)).all(|pass| pass).await;
    // In `futures-lite`, use `then` and pass the result to `all`.
    my_stream.then(|x| my_async_fn(x)).all(|pass| pass).await;
  5. Replace combinators with async/await syntax

    master

    To keep the API surface small, futures-lite does not implement many combinators that can be easily expressed using async/await. Instead of using methods like .map(), you should use an async block to process the result of a future.

    Example: Replacing .map() Instead of:

    let mapped_future = my_future.map(|x| x + 1);

    Use:

    let mapped_future = async move { my_future.await + 1 };

    Example: Replacing .and_then() Instead of using TryFutureExt methods, combine async/await with simpler combinators:

    let my_future = async { Ok(2) };
    let and_then = async move {
        let x = my_future.await;
        x.and_then(|x| x + 1)
    };
    let my_future = async { 1 };
    
    // Add one to the result of `my_future` using async/await instead of .map()
    let mapped_future = async move { my_future.await + 1 };
    
    assert_eq!(mapped_future.await, 2);
  6. Returning futures from traits using async blocks

    master

    Because async blocks are not named types, they cannot be directly returned if a trait (like Service) requires a specific named future type.

    If you encounter this, you can box the future and return a dynamic dispatch object using .boxed_local(). Note that this introduces non-trivial overhead.

    impl Service for MyService {
        type Future = Pin<Box<dyn Future<Output = i32>>>;
    
        fn call(&mut self) -> Self::Future {
            async { 1 + 1 }.boxed_local()
        }
    }

    Note: Future improvements like async fn in traits and TAIT are expected to resolve this pattern requirement.

    impl Service for MyService {
        type Future = Pin<Box<dyn Future<Output = i32>>>;
    
        fn call(&mut self) -> Self::Future {
            async { 1 + 1 }.boxed_local()
        }
    }
  7. Run an async block with future::block_on

    master

    Use futures_lite::future::block_on to execute a Future on the current thread by blocking until it completes. This is a common way to bridge synchronous code (like a main function) with asynchronous logic.

    use futures_lite::future;
    
    fn main() {
        future::block_on(async {
            println!("Hello world!");
        })
    }
  8. Reference of out-of-scope modules

    master

    futures-lite focuses on a minimal API. For advanced features, use the following specialized crates:

    FeatureRecommended Crate
    MPMC Channelsasync-channel
    Oneshot Channelsoneshot
    Mutex/Lockingasync-lock
    Atomic Wakersatomic-waker
    Executorsasync-executor
  9. Convert a Stream into a blocking Iterator with `stream::block_on`

    master

    If you are in a synchronous context and need to consume an asynchronous Stream, use stream::block_on. This converts the stream into a standard Iterator by blocking the current thread on each next() call.

    Note: This requires the std feature to be enabled.

    use futures_lite::{pin, stream};
    
    let stream = stream::once(7);
    pin!(stream);
    
    let mut iter = stream::block_on(stream);
    assert_eq!(iter.next(), Some(7));
    assert_eq!(iter.next(), None);
  10. Use `AsyncBufReadExt` for buffered reading

    master

    The AsyncBufReadExt trait provides high-level utilities for reading from types that implement AsyncBufRead.

    Common methods:

    • fill_buf(): Returns a future that resolves to the current contents of the internal buffer.
    • consume(amt): Advances the cursor by amt bytes without performing I/O.
    • read_until(byte, buf): Reads bytes into buf until the specified byte or EOF is reached.
    • read_line(buf): Reads bytes into a String until a newline (0xA) or EOF is reached.
    • lines(): Returns a Stream of String items, where each item is a line from the reader (with newline delimiters stripped).
    • split(byte): Returns a Stream of Vec<u8> items, split by the specified byte (with the delimiter stripped).
    use futures_lite::io::{AsyncBufReadExt, BufReader};
    use futures_lite::stream::StreamExt;
    
    // Example: Reading lines from a stream
    let input: &[u8] = b"hello\nworld\n";
    let mut reader = BufReader::new(input);
    let mut lines = reader.lines();
    
    while let Some(line) = lines.next().await {
        println!("{}", line?);
    }
    
    // Example: Splitting by a delimiter
    let cursor = Cursor::new(b"lorem-ipsum-dolor");
    let items: Vec<Vec<u8>> = cursor.split(b'-').try_collect().await?;
    // items == [b"lorem", b"ipsum", b"dolor"]
  11. Create infinite or custom streams with `stream::repeat`, `stream::repeat_with`, and `stream::unfold`

    master

    For more complex stream generation:

    • stream::repeat(item): Yields the same Cloneable item infinitely.
    • stream::repeat_with(f): Yields items produced by a closure f infinitely.
    • stream::unfold(seed, f): Creates a stream from a seed value and an async closure. The closure f returns Option<(Item, NextSeed)> to determine the next item and state.
    use futures_lite::stream::{self, StreamExt};
    
    // Infinite repeat
    let mut s_rep = stream::repeat(7);
    
    // Infinite repeat with closure
    let mut s_rep_w = stream::repeat_with(|| 7);
    
    // Stream from async state unfolding
    let s = stream::unfold(0, |mut n| async move {
        if n < 2 {
            let m = n + 1;
            Some((n, m))
        } else {
            None
        }
    });
  12. Repeat a stream indefinitely with `cycle`

    master

    The cycle method creates a new stream that repeats the original stream from the beginning, forever. Note that the underlying stream must implement Clone.

    use futures_lite::stream::{self, StreamExt};
    
    let s = stream::iter(vec![1, 2]).cycle();
    
    assert_eq!(s.next().await, Some(1));
    assert_eq!(s.next().await, Some(2));
    assert_eq!(s.next().await, Some(1));
    assert_eq!(s.next().await, Some(2));