agnostic-lite 0.7.0

`agnostic-lite` is an agnostic abstraction layer for any async runtime.
Documentation

agnostic-lite is an agnostic abstraction layer for any async runtime.

In order to make it trivial for others to build implementations of any async runtime, this crate provides an abstraction layer implementation.

In addition, the abstraction layer itself is no_std and alloc-free, so the traits can be used in environments where alloc is not available. It also has no unsafe code. Concrete runtime backends have their own requirements: tokio, smol, and wasm require std, while the embassy backend is no_std but requires alloc.

Introduction

agnostic-lite is a lightweight, no_std-compatible, allocation-free abstraction layer for async runtimes. It provides the essential async primitives you need to write runtime-agnostic code that works in any environment - from standard applications to embedded systems.

Key Features

  • no_std Compatible: Works without the standard library
  • Allocation-Free: No heap allocations required
  • No Unsafe Code: #![forbid(unsafe_code)] ensures memory safety
  • Modular Traits: Small, focused traits instead of one monolithic Runtime trait
  • Zero-Cost: Compiles to runtime-specific code
  • WASM Support: Works in WebAssembly environments

Core Traits

agnostic-lite provides focused traits for specific async operations:

  • AsyncSpawner: Spawn tasks globally
  • AsyncLocalSpawner: Spawn thread-local tasks
  • AsyncSleep: Sleep for a duration
  • AsyncInterval: Create periodic intervals
  • AsyncTimeout: Apply timeouts to operations
  • LocalRuntimeLite: The thread-pinned runtime core — construction, block_on, local/blocking spawning, and the !Send-tolerant time family
  • RuntimeLite: The Send extension of LocalRuntimeLite — multithread spawning and the Send time family (bounds on it reach every core item too)
  • Yielder: Yield control back to the runtime

Supported Runtimes

  • tokio - Enable with features = ["tokio"] (requires std)
  • smol - Enable with features = ["smol"] (requires std)
  • wasm-bindgen-futures - Enable with features = ["wasm"] (requires std)
  • embassy - Enable with features = ["embassy"]; the no_std runtime for embedded targets (requires alloc)

Why agnostic-lite?

Choose agnostic-lite over agnostic when:

  • ✅ You need no_std support for embedded systems
  • ✅ You want minimal dependencies and compile times
  • ✅ You only need basic async primitives (spawning, time)
  • ✅ You're building a library and want minimal footprint
  • ✅ You need guaranteed memory safety (no unsafe code)

Choose agnostic when:

  • You need networking, DNS, or process management
  • You're building standard applications with std
  • You want a batteries-included experience

Installation

[dependencies]
agnostic-lite = "0.7"

Runtime Selection

Choose one runtime feature:

# With tokio
agnostic-lite = { version = "0.7", features = ["tokio"] }

# With smol
agnostic-lite = { version = "0.7", features = ["smol"] }

# With WASM
agnostic-lite = { version = "0.7", features = ["wasm"] }

no_std Usage

The tokio, smol, and wasm runtimes all pull in std. For a real no_std runtime, use the embassy backend, which targets embassy-executor and only requires alloc:

# no_std (with alloc), for embedded targets running on embassy-executor
agnostic-lite = { version = "0.7", default-features = false, features = ["embassy"] }

The embassy backend has caveats (a one-time spawner init, a bounded task pool, a busy-polling block_on, and panicking spawn_blocking/local spawning) — see the embassy module docs.

Feature Flags

Core Features

  • std (default): Standard library support
  • alloc: Allocation support (without std)
  • time: Time-related traits and types

Runtime Features (choose one)

  • tokio: Tokio runtime implementations (requires std)
  • async-io: async-io backend (used by smol, requires std)
  • smol: Smol runtime implementations (requires std)
  • wasm: WebAssembly support via wasm-bindgen-futures (requires std)
  • embassy: no_std runtime for embedded targets via embassy-executor (requires alloc)

Trait Reference

The signatures below are simplified sketches for orientation; see docs.rs for the authoritative definitions.

AsyncSpawner

pub trait AsyncSpawner: Yielder + Copy + Send + Sync + 'static {
    type JoinHandle<O>: JoinHandle<O> + Send + Sync + 'static
    where
        O: Send + 'static;

    fn spawn<F>(future: F) -> Self::JoinHandle<F::Output>
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static;
}

AsyncLocalSpawner

pub trait AsyncLocalSpawner: Yielder + Copy + 'static {
    type JoinHandle<O>: LocalJoinHandle<O> + 'static
    where
        O: 'static;

    fn spawn_local<F>(future: F) -> Self::JoinHandle<F::Output>
    where
        F: Future + 'static,
        F::Output: 'static;
}

AsyncSleep

pub trait AsyncSleep: Future<Output = Self::Instant> + Send {
    type Instant: Instant;

    fn reset(self: Pin<&mut Self>, deadline: Self::Instant);
}

AsyncInterval

pub trait AsyncInterval: Stream<Item = Self::Instant> + Send + Unpin {
    type Instant: Instant;

    fn reset(&mut self, interval: Duration);
    fn reset_at(&mut self, instant: Self::Instant);
    fn poll_tick(&mut self, cx: &mut Context<'_>) -> Poll<Self::Instant>;
}

AsyncTimeout

pub trait AsyncTimeout<F>: Future<Output = Result<F::Output, Elapsed>> + Send
where
    F: Future + Send,
{
    type Instant: Instant;

    fn timeout(timeout: Duration, fut: F) -> Self;
    fn timeout_at(deadline: Self::Instant, fut: F) -> Self;
}

LocalRuntimeLite and RuntimeLite

Since 0.7 the runtime abstraction is two traits. LocalRuntimeLite is the thread-pinned core — everything a consumer needs to host futures on the current thread — and RuntimeLite is its Send extension. A thread-pinned host (a LocalSet-shaped executor, or any runtime whose timers are deliberately !Send) can implement the core alone; tokio/smol/wasm/embassy implement both. Completion-based (proactor) runtimes such as compio are deliberately out of scope: their I/O model warrants a native driver integration of its own, not this reactor-shaped abstraction wrapped around it. This is a trimmed sketch:

pub trait LocalRuntimeLite: Sized + Unpin + Copy + Send + Sync + 'static {
    type LocalSpawner: AsyncLocalSpawner;
    type BlockingSpawner: AsyncBlockingSpawner;
    // with the `time` feature: Instant, LocalSleep, LocalInterval, LocalTimeout, LocalDelay

    fn new() -> Self;
    fn block_on<F: Future>(f: F) -> F::Output;
    // ... plus spawn_local / spawn_blocking / sleep_local / timeout_local and more
}

pub trait RuntimeLite: LocalRuntimeLite {
    type Spawner: AsyncSpawner;
    // with the `time` feature: AfterSpawner, Sleep, Interval, Timeout, Delay

    fn spawn<F>(future: F) -> <Self::Spawner as AsyncSpawner>::JoinHandle<F::Output>
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static;
    // ... plus sleep / timeout / interval / spawn_after and more
}

Generic R: RuntimeLite bounds reach every core item through the supertrait unchanged. Concrete-type calls of moved members (SmolRuntime::block_on(..)) need LocalRuntimeLite in scope, and UFCS calls must name the owning trait.

Conditional Compilation Helpers

agnostic-lite provides macros for conditional compilation:

use agnostic_lite::{cfg_tokio, cfg_smol};

cfg_tokio! {
    // This code only compiles when tokio feature is enabled
    use tokio::task;
}

cfg_smol! {
    // This code only compiles when smol feature is enabled
    use smol::Task;
}

Performance

agnostic-lite has zero runtime overhead. All trait methods are inlined and compile to the same code as using the runtime directly:

  • No allocations: Works without heap allocations
  • No dynamic dispatch: All trait calls are statically resolved
  • Zero-cost abstractions: Compiles to identical assembly as direct runtime usage

License

agnostic-lite is under the terms of both the MIT license and the Apache License (Version 2.0).

See LICENSE-APACHE, LICENSE-MIT for details.

Copyright (c) 2025 Al Liu.