async-foundation 0.2.1

Foundational async primitives for Rust - timers, networking, and common utilities
Documentation

async-foundation

Foundational async primitives for Rust – lightweight timers, networking utilities, and common async building blocks designed for low overhead and predictable performance.

What this crate is

  • Focused building blocks: Provides a small, well-defined set of primitives (timer, net, and common utilities) rather than a full async runtime.
  • Runtime-agnostic: Works with standard futures and can be integrated into custom executors or existing ecosystems.
  • Performance-oriented: Comes with a comprehensive Criterion benchmark suite for timers, networking, and internal data structures.

Core components

  • Timers (async_foundation::timer)
    • High-level Timer abstraction for scheduling waits with millisecond-level control.
    • Designed to handle both single-shot and many concurrent timers efficiently.
  • Networking (async_foundation::net)
    • TcpReadStream / TcpWriteStream thin async wrappers around non-async mio types, adding Future/AsyncRead/AsyncWrite behavior with minimal overhead compared to using mio directly.
    • UdpSocket async wrapper over standard UDP sockets.
  • Common utilities (async_foundation::common)
    • ReadyFuture and related state/observable types for composing async workflows.

For a full API overview, see the crate docs once published on docs.rs.

Examples

Timer

use async_foundation::timer::timer::Timer;
use futures::executor::block_on;
use std::time::Duration;

let mut timer = Timer::new();
block_on(async {
    // Wait for 100ms
    timer.wait(Duration::from_millis(100)).await;
    
    // Multiple concurrent timers
    let t1 = timer.wait(Duration::from_millis(50));
    let t2 = timer.wait(Duration::from_millis(100));
    futures::join!(t1, t2);
});

TCP Networking

use async_foundation::net::tcp_stream::TcpStream;
use futures::executor::block_on;
use futures::{AsyncReadExt, AsyncWriteExt};
use std::net::TcpListener;
use std::thread;

// Server side
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
thread::spawn(move || {
    if let Ok((stream, _)) = listener.accept() {
        // Handle connection
    }
});

// Client side
block_on(async {
    let mut stream = TcpStream::connect(addr).unwrap();
    
    // Write data
    stream.write_all(b"Hello, world!").await.unwrap();
    
    // Read data
    let mut buf = [0u8; 1024];
    let n = stream.read(&mut buf).await.unwrap();
});

UDP Networking

use async_foundation::net::udp_socket::UdpSocket;
use futures::executor::block_on;
use std::net::SocketAddr;

block_on(async {
    // Bind a UDP socket (non-blocking)
    let socket = UdpSocket::bind("127.0.0.1:0").unwrap();
    
    // Send data (non-blocking, can be used in async context)
    let target: SocketAddr = "127.0.0.1:8080".parse().unwrap();
    socket.send_to(b"Hello", target).unwrap();
    
    // Receive data (non-blocking)
    let mut buf = [0u8; 1024];
    let (n, from) = socket.recv_from(&mut buf).unwrap();
    
    // UdpSocket also implements AsyncRead/AsyncWrite for stream-like usage
});

ReadyFuture

use async_foundation::common::ready_future::ReadyFuture;
use futures::executor::block_on;

// Create a future that can be completed later
let future = ReadyFuture::new();
let future_clone = future.clone();

// Complete it from another context
future.complete(42);

// Await the result
let result = block_on(future_clone);
match result {
    async_foundation::common::ready_future_state::ReadyFutureResult::Completed(value) => {
        assert_eq!(value, 42);
    }
    _ => unreachable!(),
}

// Or create a pre-completed future
let completed = ReadyFuture::new_completed("done");
let result = block_on(completed);

ReadyObservable

use async_foundation::common::ready_observable::ReadyObservable;
use futures::executor::block_on;

let mut observable = ReadyObservable::default();

// Multiple tasks can wait for the observable to become ready
let wait1 = observable.wait();
let wait2 = observable.wait();

// Complete the observable (wakes all waiters)
observable.complete();

// Both futures will now resolve
block_on(async {
    futures::join!(wait1, wait2);
});

Performance characteristics

This crate is designed for low overhead and predictable performance:

  • Timers: More efficient than std::thread::sleep for async contexts, with lower overhead than heavyweight runtime timers. Optimized for handling many concurrent timers efficiently.
  • Networking: Thin wrappers around mio with minimal overhead compared to direct mio usage, while providing a more ergonomic async API.
  • Memory: Low allocation overhead for timers and networking primitives, making it suitable for high-throughput scenarios.

How this compares to other solutions

This crate does not aim to replace full-featured async runtimes like Tokio, async-std, or smol. Instead, it targets a specific niche:

  • You want lower-level control over timers and I/O than high-level runtimes typically expose.
  • You want small, composable primitives that can be wired into your own executor or integrated into existing systems.
  • You care about predictable overhead and want benchmarks baked into the project so you can reason about performance over time.

In practice:

  • Compared to using std::thread::sleep or ad-hoc timer wheels, async-foundation:
    • Provides low-overhead timer creation with explicit, benchmarked behavior for concurrent and sequential timers.
  • Compared to using only high-level runtime timers (e.g. tokio::time::sleep):
    • This crate gives you more visibility into timer internals (via timer_state and timer_future_state).
    • It is easier to embed in minimal or experimental executors where you don’t want to pull in a full runtime.
  • For networking, the focus is on:
    • Providing thin, benchmarked wrappers around mio and standard sockets.
    • Making it straightforward to measure and reason about the overhead of stream/socket wrappers and timeout handling.

Use async-foundation when you:

  • Are building your own async runtime or executor.
  • Need fine-grained control over timers and timeouts.
  • Want a small, well-benchmarked layer for async networking primitives without a heavyweight dependency tree.

Use a full runtime (Tokio, async-std, etc.) when you:

  • Need batteries-included features (spawning, timers, I/O, synchronization, etc.) in a single crate.
  • Prefer convenience over having to stitch together lower-level components yourself.

License

This project is licensed under the MIT License. See LICENSE for details.