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 uses one shared completion trait with separate Send-capable and thread-local execution traits:
#![allow(unused)]
fn main() {
pub trait RuntimeCompletion: 'static {
type Sleep: Future<Output = ()>;
fn sleep(duration: Duration) -> Self::Sleep;
fn block_on<F: Future>(future: F) -> Result<F::Output, aioduct::Error>;
}
pub trait RuntimePoll: RuntimeCompletion<Sleep: Send> + Send + Sync {
fn spawn_send<F>(future: F)
where
F: Future<Output = ()> + Send + 'static;
}
pub trait RuntimeLocal: RuntimeCompletion {
fn spawn_local<F>(future: F)
where
F: Future<Output = ()> + 'static;
}
}
RuntimeCompletion (Base)
The foundation trait. It defines the runtime’s sleep future and provides
block_on to drive a future to completion on a new runtime instance. Runtime
construction can fail, so block_on returns Result. Every native runtime
implements this trait.
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.
Its RuntimeCompletion::Sleep future must also be Send.
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.
Its RuntimeCompletion::Sleep future does not need to be Send.
Implemented by CompioRuntime.
Connector Traits
Networking is decoupled from the runtime via connector traits. DNS resolution
happens before connector dispatch, so each connector establishes a stream to a
pre-resolved SocketAddr.
#![allow(unused)]
fn main() {
pub trait ConnectorSend: Clone + Send + Sync + 'static {
type Stream: hyper::rt::Read
+ hyper::rt::Write
+ SocketConfig
+ Unpin
+ Send
+ 'static;
fn connect(
&self,
addr: SocketAddr,
) -> impl Future<Output = io::Result<Self::Stream>> + Send;
}
pub trait ConnectorLocal: 'static {
type Stream: hyper::rt::Read
+ hyper::rt::Write
+ SocketConfig
+ Unpin
+ 'static;
async fn connect(&self, addr: SocketAddr) -> 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.
Both connector traits also support connect_bound for a requested local
address and from_std_tcp for adopting a socket created by Happy Eyeballs.
SocketConfig
Connector stream types implement SocketConfig. The engine uses that trait
after connection establishment to apply TCP keepalive, TCP Fast Open, and
interface binding where the platform supports them. Destination addresses and
local bind addresses are passed to connector methods rather than stored in a
configuration object.
Built-in Implementations
TokioRuntime + TcpConnector
Enabled with features = ["tokio"].
#![allow(unused)]
fn main() {
use aioduct::TokioClient;
let client = TokioClient::new();
}
TokioRuntimecreates a current-thread runtime forblock_on, rejects blocking use from inside an active Tokio runtime, and implementsRuntimePollwithtokio::spawnplustokio::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();
}
SmolRuntimeimplementsRuntimeCompletionandRuntimePollusingsmol::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.
CompioRuntimecreates acompio_runtime::Runtimeforblock_on, usescompio_runtime::spawnfor local tasks, and wrapsasync_io::Timerfor the shared runtime sleep contract.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.
HTTP/2 Task Executors
hyper’s HTTP/2 client handshake requires an Executor to drive background
connection tasks. aioduct uses separate internal executors for the two runtime
models: PollExecutor<R> delegates to RuntimePoll::spawn_send, while
CompletionExecutor<R> delegates to RuntimeLocal::spawn_local.
Both use PhantomData<fn() -> R> so the executor type does not inherit
unnecessary ownership or auto-trait bounds from the runtime marker. These
executors are internal connection-lifecycle machinery, not part of the public
runtime trait contract.
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.