Crate corona [] [src]

A library combining futures and coroutines.

The current aim of Rust in regards to asynchronous programming is on futures. They have many good properties. However, they tend to result in very functional-looking code (eg. monadic chaining of closures through their modifiers). Some tasks are more conveniently done throuch imperative approaches.

The aim of this library is to integrate coroutines with futures. It is possible to start a coroutine. The coroutine can wait for a future to complete (which'll suspend its execution, but will not block the thread ‒ the execution will switch to other futures or coroutines on the same thread). A spawned coroutine is represented through a handle that acts as a future, representing its completion. Therefore, other coroutines can wait for it, or it can be used in the usual functional way and compose it with other futures.

Unlike the coroutines planned for core Rust, these coroutines are stack-full (eg. you can suspend them from within deep stack frame) and they are available now.

The cost

The coroutines are not zero cost, at least not now. There are these costs:

  • Each coroutine needs a stack. The stack takes some space and is allocated dynamically. Furthermore, a stack can't be allocated through the usual allocator, but is mapped directly by the OS and manipulating the memory mapping of pages is relatively expensive operation (a syscall, TLB needs to be flushed, ...). The stacks are cached and reused, but still, creating them has a cost.
  • Each wait for a future inside a coroutine allocates dynamically (because it spawns a task inside Tokio's reactor core.

Some of these costs might be mitigated or lowered in future, but for now, expect to get somewhat lower performance with coroutines compared to using only futures.

API Stability

Currently, the crate is in an experimental state. It exists mostly as a research if it is possible to provide something like async/await in Rust and integrate it with the current asynchronous stack, without adding explicit support to the language.

It is obviously possible, but the ergonomics is something that needs some work. Therefore, expect the API to change, possibly in large ways.

Still, if you want to test it, it should probably work and might be useful. It is experimental in a sense the API is not stabilized, but it is not expected to eat data or crash.

Known problems

These are the problems I'm aware of and which I want to find a solution some day.

  • The current API is probably inconvinient.
  • Many abstractions are missing. Things like waiting for a future with a timeout, or waiting for the first of many futures or streams would come handy.
  • Many places have 'static bounds on the types, even though these shouldn't be needed in theory.
  • The relation with unwind safety is unclear.
  • No support for threads (probably not even possible ‒ Rust's type system doesn't expect a stack to move from one thread to another).
  • It relies on the tokio. It would be great if it worked with other future executors as well.
  • Cleaning up of stacks (and things on them) when the coroutines didn't finish yet is done through panicking. This has some ugly side effects.
  • It is possible to create a deadlock when moving the driving tokio core inside a coroutine, like this (eg. this is an example what not to do):
let mut core = Core::new().unwrap();
let (sender, receiver) = oneshot::channel();
let handle = core.handle();
let c = Coroutine::with_defaults(handle.clone(), move |_await| {
    core.run(receiver).unwrap();
});
Coroutine::with_defaults(handle, |await| {
    let timeout = Timeout::new(Duration::from_millis(50), await.handle()).unwrap();
    await.future(timeout).unwrap();
    drop(sender.send(42));
});
c.wait().unwrap();

Contribution

All kinds of contributions are welcome, including reporting bugs, improving the documentation, submitting code, etc. However, the most valuable contribution for now would be trying it out and providing some feedback ‒ if the thing works, where the API needs improvements, etc.

Examples

One that shows the API.

use std::time::Duration;
use corona::Coroutine;
use futures::Future;
use tokio_core::reactor::{Core, Timeout};

let mut core = Core::new().unwrap();
let builder = Coroutine::new(core.handle());
let generator = builder.generator(|producer| {
    producer.produce(1);
    producer.produce(2);
});
let coroutine = builder.spawn(move |await| {
    for item in await.stream(generator) {
        println!("{}", item.unwrap());
    }

    let timeout = Timeout::new(Duration::from_millis(100), await.handle()).unwrap();
    await.future(timeout).unwrap();

    42
});
assert_eq!(42, core.run(coroutine).unwrap());

Further examples can be found in the repository.

Modules

results

Structs

Await

An asynchronous context.

Coroutine

A builder of coroutines.

Dropped

An error marker when a future is dropped before having change to get resolved.

Producer

This is an extended Await with ability to items.

Enums

TaskFailed

The task (coroutine) has failed.