Crate agnostik[][src]


Agnostik is a layer between your application and the executor that is used to execute futures. It allows you to switch between the executors smoothly and without having to change much code.

You can use agnostik in every library that requires an executor, but wants to let the user decide which executor should be used. You can also use agnostik in an application, if you plan to use multiple executors, or want to switch between executors.


  • Run futures and wait for them to finish
  • Spawn futures using the underlying executor
  • Spawn blocking tasks in threads that are able to execute blocking methods

Every feature I just said, can be used with every executor provided by agnostik, or you can integrate your own executor with Agnostik.

Get started

Check the tests for simple examples.

If you have cargo-edit installed, you can just execute this:

cargo add agnostik

otherwise, add this to your Cargo.toml file

agnostik = "0.1.0"


Switching executors

You can choose the executor, by using cargo features. The default runtime is the bastion-executor. To use another executor you just have to disable the default features, and choose one of the valid features. Valid features are:

  • runtime_bastion (default) to use the Bastion Executor
  • runtime_tokio to use the Tokio runtime
  • runtime_asyncstd to use the AsyncStd runtime
  • runtime_nostd (coming soon) to use Agnostik in a no_std environment

E.g. to use the Tokio runtime, add the following line to your Cargo.toml

agnostik = { version = "0.1.0", default-features = false, features = ["runtime_tokio"]}


Agnostiks API is very easy and only has a few methods to use. Here's an example with the bastion-executor.

use agnostik::prelude::*;

fn main() {
    let runtime = Agnostik::bastion();

    let future = runtime.spawn(async {
        println!("Hello from bastions executor!");

    let future = runtime.spawn_blocking(|| {

There's also a global executor instance that can be used to spawn futures without creating and storing your own executor.

fn main() {
    let result = agnostik::block_on(async {
        agnostik::spawn(async {
            println!("Hello from bastion executor!");
    assert_eq!(result, 1);

or use the attribute macros.

async fn main() {
    println!("hello world");

If you want to use another exceutor, you just have to replace the Agnostik::bastion() method call, with the method that corresponds to your executor.


  • Agnostik::bastion() for bastion
  • Agnostik::async_std() for async std
  • Agnostik::tokio() for tokio. Warning: See "How to use tokio runtime"
  • Agnostik::tokio_with_runtime(runtime) if you want to use your own tokio::runtime::Runtime object. Warning: See "How to use tokio runtime"
  • Agnostik::no_std() (coming soon) to create an exeutor that works in a nostd environment

How to use tokio runtime

It's not supported to use the tokio::main macro together with agnostik, because Agnostik requires a Runtime object, which is created by calling Runtime::new(). If your are using the tokio::main macro, there will be a panic, because you can't create a runtime inside a runtime.

Here's how to fix it:

use agnostik::prelude::*;

async fn main() {
    let runtime = Agnostik::tokio();

    let result = runtime.spawn(async_task()).await;

    println!("The result is {}", result)

This would fail with a panic. How to do it correctly:

use agnostik::prelude::*;
use tokio::runtime::Runtime;

fn main() {
    // see tokio docs for more methods to create a runtime
    let runtime = Runtime::new().expect("Failed to create a runtime"); // 1
    let runtime = Agnostik::tokio_with_runtime(runtime); // 2

    let result = runtime.spawn(async_task());
    let result = runtime.block_on(result);

    println!("The result is {}", result)

You can replace 1 and 2 with Agnostik::tokio(), because this method call will create a Runtime object using Runtime::new().



If a runtime flag is provided, the type for the specific executor will be re-exported here.


Generic join handle type.


A prelude for the agnostik crate.



This struct doesn't have any functionality. It's only use is to have a nice API to create executors for different runtimes.



and wait for a future to finish.


This trait represents an executor that is capable of spawning futures onto the same thread.



block_on will use the global executor instance, which is determined by the cargo features, to block until the given future has finished.


Returns a reference to the global executor.


spawn will use the global executor instance, which is determined by the cargo features, to spawn the given future.


spawn_blocking will use the global executor instance, which is determined by the cargo features, to spawn the given blocking task.