Expand description
Compatibility adapter between tokio and futures.
There are two kinds of compatibility issues between tokio and futures:
- Tokio’s types cannot be used outside tokio context, so any attempt to use them will panic.
- Solution: If you apply the
Compatadapter to a future, the future will manually enter the context of a global tokio runtime. If a runtime is already available via tokio thread-locals, then it will be used. Otherwise, a default single-threaded runtime will be created on demand. That does not mean the future is polled by the tokio runtime - it only means the future sets a thread-local variable pointing to the global tokio runtime so that tokio’s types can be used inside it.
- Solution: If you apply the
- Tokio and futures have similar but different I/O traits
AsyncRead,AsyncWrite,AsyncBufRead, andAsyncSeek.- Solution: When the
Compatadapter is applied to an I/O type, it will implement traits of the opposite kind. That’s how you can use tokio-based types wherever futures-based types are expected, and the other way around.
- Solution: When the
You can apply the Compat adapter using the Compat::new() constructor or using any
method from the CompatExt trait.
§The multi-thread feature
Because the default fallback runtime is single-threaded, a task spawned within a Compat
future runs on a current-thread scheduler, where tokio::task::block_in_place panics with
can call blocking only when running on the multi-threaded runtime. That call is how an
async wrapper around a synchronous library runs a blocking call without stalling the tasks
queued behind it, so a dependency written that way cannot be driven through Compat
unless a multi-threaded runtime is already ambient.
Enabling the multi-thread feature backs the fallback runtime with a multi-threaded scheduler
instead, so spawned tasks run on real worker threads and block_in_place behaves as it would
in any other tokio program. The trade-off is that the runtime starts a worker pool (sized to
the available parallelism) rather than a single thread, so it is off by default.
The feature affects the process-wide fallback runtime, and cargo features are additive, so
enabling it anywhere in a dependency graph enables it for every Compat user in that binary.
Nothing that works without the feature stops working with it - a multi-threaded scheduler is
strictly more permissive - but it is worth knowing that the choice is not local to one crate.
§Configuring the fallback runtime
Call set_fallback_runtime() before anything falls back, for example to name its threads
or, with the multi-thread feature, to set its number of worker threads.
§Examples
This program reads lines from stdin and echoes them into stdout, except it’s not going to work:
fn main() -> std::io::Result<()> {
futures::executor::block_on(async {
let stdin = tokio::io::stdin();
let mut stdout = tokio::io::stdout();
// The following line will not work for two reasons:
// 1. Runtime error because stdin and stdout are used outside tokio context.
// 2. Compilation error due to mismatched `AsyncRead` and `AsyncWrite` traits.
futures::io::copy(stdin, &mut stdout).await?;
Ok(())
})
}To get around the compatibility issues, apply the Compat adapter to stdin, stdout, and
futures::io::copy():
use async_compat::CompatExt;
fn main() -> std::io::Result<()> {
futures::executor::block_on(async {
let stdin = tokio::io::stdin();
let mut stdout = tokio::io::stdout();
futures::io::copy(stdin.compat(), &mut stdout.compat_mut()).compat().await?;
Ok(())
})
}It is also possible to apply Compat to the outer future passed to
futures::executor::block_on() rather than futures::io::copy() itself.
When applied to the outer future, individual inner futures don’t need the adapter because
they’re all now inside tokio context:
use async_compat::{Compat, CompatExt};
fn main() -> std::io::Result<()> {
futures::executor::block_on(Compat::new(async {
let stdin = tokio::io::stdin();
let mut stdout = tokio::io::stdout();
futures::io::copy(stdin.compat(), &mut stdout.compat_mut()).await?;
Ok(())
}))
}The compatibility adapter converts between tokio-based and futures-based I/O types in any direction. Here’s how we can write the same program by using futures-based I/O types inside tokio:
use async_compat::CompatExt;
use blocking::Unblock;
#[tokio::main]
async fn main() -> std::io::Result<()> {
let mut stdin = Unblock::new(std::io::stdin());
let mut stdout = Unblock::new(std::io::stdout());
tokio::io::copy(&mut stdin.compat_mut(), &mut stdout.compat_mut()).await?;
Ok(())
}Finally, we can use any tokio-based crate from any other async runtime. Here are reqwest and warp as an example:
use async_compat::{Compat, CompatExt};
use warp::Filter;
fn main() {
futures::executor::block_on(Compat::new(async {
// Make an HTTP GET request.
let response = reqwest::get("https://www.rust-lang.org").await.unwrap();
println!("{}", response.text().await.unwrap());
// Start an HTTP server.
let routes = warp::any().map(|| "Hello from warp!");
warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
}))
}Structs§
- Compat
- Compatibility adapter for futures and I/O types.
Enums§
- SetFallback
Runtime Error - The error returned by
set_fallback_runtime().
Traits§
Functions§
- set_
fallback_ runtime - Configures the runtime that
Compatuses when it is polled outside a tokio context.