Runtime and Connector Traits
aioduct is runtime-agnostic. The runtime and connector traits define the minimal interfaces that an async runtime and its networking layer must provide.
Runtime Trait Hierarchy
The runtime system is split into a three-level trait hierarchy, from most general to most capable:
#![allow(unused)]
fn main() {
pub trait RuntimeCompletion: 'static {
fn block_on<F: Future>(future: F) -> F::Output;
}
pub trait RuntimePoll: RuntimeCompletion {
fn spawn_send<F>(future: F)
where
F: Future<Output = ()> + Send + 'static;
fn sleep(duration: Duration) -> impl Future<Output = ()> + Send;
}
pub trait RuntimeLocal: RuntimeCompletion {
fn spawn_local<F>(future: F)
where
F: Future<Output = ()> + 'static;
fn sleep(duration: Duration) -> impl Future<Output = ()>;
}
}
RuntimeCompletion (Base)
The foundation trait. Provides block_on to drive a future to completion synchronously. Every runtime implements this.
RuntimePoll (Send-capable runtimes)
Extends RuntimeCompletion with:
spawn_send: Spawn aSendfuture as a detached background task. Used for driving hyper connection futures on work-stealing schedulers.sleep: Create aSendsleep future for the given duration. Used for timeouts and pool idle eviction.
Implemented by TokioRuntime and SmolRuntime.
RuntimeLocal (Thread-local runtimes)
Extends RuntimeCompletion with:
spawn_local: Spawn a!Sendfuture on the current thread. Used for thread-per-core runtimes where tasks never cross thread boundaries.sleep: Create a sleep future (not required to beSend).
Implemented by CompioRuntime.
Connector Traits
Networking is decoupled from the runtime via connector traits. Each connector is responsible for establishing a TCP connection given a SocketConfig.
#![allow(unused)]
fn main() {
pub trait ConnectorSend: Clone + Send + Sync + 'static {
type Stream: AsyncRead + AsyncWrite + Unpin + Send + 'static;
fn connect(&self, config: &SocketConfig) -> impl Future<Output = io::Result<Self::Stream>> + Send;
}
pub trait ConnectorLocal: 'static {
type Stream: AsyncRead + AsyncWrite + Unpin + 'static;
fn connect(&self, config: &SocketConfig) -> impl Future<Output = io::Result<Self::Stream>>;
}
}
ConnectorSend
For use with HttpEngineSend<R, C>. Must be Clone + Send + Sync so it can be shared across tasks on a work-stealing scheduler. The returned stream must be Send.
ConnectorLocal
For use with HttpEngineLocal<R, C>. No Send bounds — the connector and its streams live on a single thread.
SocketConfig
Both connector traits receive a SocketConfig that contains the target address, port, DNS resolution hints, TCP options (nodelay, keepalive), and local bind address.
Built-in Implementations
TokioRuntime + TcpConnector
Enabled with features = ["tokio"].
#![allow(unused)]
fn main() {
use aioduct::TokioClient;
let client = TokioClient::new();
}
TokioRuntimeimplementsRuntimePollusingtokio::runtime::Handle::block_on,tokio::spawn, andtokio::time::sleep.tokio_rt::TcpConnectorimplementsConnectorSendusingtokio::net::TcpStream. SetsTCP_NODELAYby default.- The
TokioIoadapter bridges tokio’sAsyncRead/AsyncWriteto hyper’srt::Read/rt::Write.
SmolRuntime + TcpConnector
Enabled with features = ["smol"].
#![allow(unused)]
fn main() {
use aioduct::SmolClient;
let client = SmolClient::new();
}
SmolRuntimeimplementsRuntimePollusingsmol::block_on,smol::spawn, andasync_io::Timer.smol_rt::TcpConnectorimplementsConnectorSendusingsmol::net::TcpStream.- The
SmolIoadapter bridgesfutures_io::AsyncRead/AsyncWriteto hyper’s traits.
CompioRuntime + TcpConnector (Experimental)
Enabled with features = ["compio"].
#![allow(unused)]
fn main() {
use aioduct::CompioClient;
compio_runtime::Runtime::new().unwrap().block_on(async {
let client = CompioClient::new();
let resp = client.get("http://httpbin.org/get")?.send().await?;
println!("status: {}", resp.status());
Ok::<_, aioduct::Error>(())
});
}
Compio is a completion-based I/O runtime (io_uring on Linux, IOCP on Windows) with a thread-per-core execution model.
CompioRuntimeimplementsRuntimeLocalusingcompio_runtime::block_on,compio_runtime::spawn, and compio’s native timers.compio_rt::TcpConnectorimplementsConnectorLocal. Streams are!Sendsince they are bound to the completion ring of the current thread.
Important: compio futures are !Send (they cannot be sent between threads). The CompioClient type alias uses HttpEngineLocal, which does not require Send bounds on futures or streams. This is safe because compio’s thread-per-core model guarantees futures never cross thread boundaries.
HyperExecutor
hyper’s HTTP/2 handshake requires an Executor to spawn background tasks for connection management. aioduct provides a generic HyperExecutor<R> that delegates to R::spawn_send (for RuntimePoll) or R::spawn_local (for RuntimeLocal):
#![allow(unused)]
fn main() {
pub struct HyperExecutor<R>(PhantomData<fn() -> R>);
}
The PhantomData<fn() -> R> (rather than PhantomData<R>) ensures HyperExecutor is always Unpin regardless of R, which hyper’s h2 handshake requires.
Implementing a Custom Runtime
To add a new poll-based runtime:
- Implement
RuntimeCompletionandRuntimePollfor your runtime marker type. - Implement
ConnectorSendfor a connector struct that establishes TCP connections using your runtime’s networking primitives. - Provide an IO adapter that implements
hyper::rt::Readandhyper::rt::Writeby delegating to your runtime’s native async IO traits.
For a thread-local runtime, implement RuntimeCompletion and RuntimeLocal instead, with a ConnectorLocal implementation.
See src/runtime/tokio_rt.rs for a reference RuntimePoll + ConnectorSend implementation.