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 |
Enums
TaskFailed |
The task (coroutine) has failed. |