cocoro/lib.rs
1//! The `cocoro` crate shows a different approach to coroutines in Rust from the
2//! one in `std::ops` that's guarded by the `coroutine_trait` feature.
3//!
4//! A *coroutine* is a state machine that may consume "input" values one at a
5//! time, and each time "yields" a value *or* "returns" with a final value. A
6//! coroutine can yield arbitrarily many times but may only return once.
7//!
8//! In this crate, the core coroutine trait looks like:
9//!
10//! ```rust
11//! pub trait SuspendedVisitor<Y, R, I, N>
12//! where
13//! N: Coro<Y, R, I>,
14//! {
15//! type Out;
16//! fn on_yield(self, y: Y, next: N) -> Self::Out;
17//! fn on_return(self, r: R) -> Self::Out;
18//! }
19//!
20//! pub trait Suspended<Y, R, I> {
21//! type Next: Coro<Y, R, I>;
22//! fn visit<X>(
23//! self,
24//! visitor: impl SuspendedVisitor<Y, R, I, Self::Next, Out = X>,
25//! ) -> X;
26//! }
27//!
28//! pub trait Coro<Y, R, I = ()>: Sized {
29//! type Next: Coro<Y, R, I>;
30//! type Suspend: Suspended<Y, R, I, Next = Self::Next>;
31//! fn resume(self, input: I) -> Self::Suspend;
32//! }
33//! ```
34//!
35//! Note the following differences from `std::ops::Coroutine`:
36//!
37//! * The `resume` method takes `self` by value, not by a pinned exclusive
38//! reference.
39//! * The types for "yield" and "return" are generic parameters rather than
40//! associated types.
41//! * The `resume` method returns a `Suspend` type that wraps the state of
42//! the coroutine, which provides to a "visitor" a handle to a coroutine
43//! that can be resumed again.
44//!
45//! The `Suspended` trait can be thought of as an abstraction over an enum.
46//! Indeed, many implementations of coroutines will use the following enum which
47//! is provided by the crate:
48//!
49//! ```rust
50//! pub enum Suspend<Y, R, N> {
51//! Yield(Y, N),
52//! Return(R),
53//! }
54//! ```
55//!
56//! The `Yield` and `Return` variants are imported into the crate's root
57//! namespace, so they can be used withoutthe `Suspend::` prefix.
58//!
59//! In addition, the `Coro` trait provides a number of default combinators that
60//! should feel familiar to anyone working with `Iterator`, for example:
61//!
62//! * `map_yield` to transform the yielded values with an `FnMut`
63//! * `map_return` to transform the return value with an `FnOnce`
64//! * `flatten` to flatten a coroutine that returns another coroutine into a
65//! single coroutine
66//!
67//! This crate makes no attempt to use fancy macros or code transformations to
68//! let you write coroutines as if they were procedural functions, as the
69//! `coroutine_trait` feature does. Instead, it is designed for a functional
70//! style where the elements yielded and returned are transformed with pipelines
71//! of combinators, with an emphasis on type-safety.
72//!
73//! # Examples
74//!
75//! ## A basic counter
76//!
77//! Here's a coroutine that yields successive integers and never returns:
78//!
79//! ```rust
80//! use cocoro::{Coro, Suspended, Yielded};
81//!
82//! struct Counter(i32);
83//! impl Coro<i32, (), ()> for Counter {
84//! type Next = Self;
85//! type Suspend = Yielded<i32, Self>;
86//! fn resume(self, _: ()) -> Self::Suspend {
87//! Yielded(self.0, Counter(self.0 + 1))
88//! }
89//! }
90//!
91//! let mut counter = Counter(0);
92//! for _ in 0..10 {
93//! let Yielded(n, next) = counter.resume(());
94//! println!("{}", n);
95//! counter = next;
96//! }
97//! ```
98//!
99//! Notice how the `Counter` struct is immutable, and the next state is
100//! returned from the `resume` method by constructing a new `Counter` instance.
101//!
102//! Because the `Next` associated type is `Self`, we are able to mutate the
103//! variable `counter` in-place. However, in the next example, we'll see a
104//! coroutine that yields with a state of a different type than itself.
105//!
106//! Furthermore, we can irrefutably match the `Yielded` struct because the
107//! `Suspend` associated type of the coroutine is known at compile time and
108//! transparent to the user. More generally, when the `Suspend` associated type
109//! of a coroutine isn't known (e.g. because it's not constrained by bounds on
110//! a function parameter or on an `impl Coro` return type from the function that
111//! the coroutine came from), the `Suspended` trait provides a `visit` method
112//! that can be converted to a `Suspend` enum to pattern-match against, or
113//! visited directly with a `SuspendedVisitor`.
114//!
115//! It's more common to use helper functions and combinators to create
116//! coroutines, rather than implement the `Coro` trait directly.
117//!
118//! Using the `yield_with()` function, we can do the same thing as above with a
119//! closure:
120//!
121//! ```rust
122//! use cocoro::{yield_with, Coro, Void, Yield};
123//! let mut i = 0;
124//! let _: Option<Void> = yield_with(|()| {
125//! i += 1;
126//! i
127//! })
128//! .take(10)
129//! .for_each(|n| {
130//! println!("{}", n);
131//! });
132//! ```
133//!
134//! ## Static-sized countdown
135//!
136//! ```rust
137//! use cocoro::{Coro, Returned, Suspend, Suspended, Void, Yielded};
138//!
139//! struct Three;
140//! struct Two;
141//! struct One;
142//! #[derive(Debug, PartialEq, Eq)]
143//! struct Blastoff;
144//!
145//! impl Coro<i32, Blastoff, ()> for Three {
146//! type Next = Two;
147//! type Suspend = Yielded<i32, Self::Next>;
148//! fn resume(self, _: ()) -> Self::Suspend {
149//! Yielded(3, Two)
150//! }
151//! }
152//!
153//! impl Coro<i32, Blastoff, ()> for Two {
154//! type Next = One;
155//! type Suspend = Yielded<i32, Self::Next>;
156//! fn resume(self, _: ()) -> Self::Suspend {
157//! Yielded(2, One)
158//! }
159//! }
160//!
161//! impl Coro<i32, Blastoff, ()> for One {
162//! type Next = Blastoff;
163//! type Suspend = Yielded<i32, Self::Next>;
164//! fn resume(self, _: ()) -> Self::Suspend {
165//! Yielded(1, Blastoff)
166//! }
167//! }
168//!
169//! impl Coro<i32, Blastoff, ()> for Blastoff {
170//! type Next = Void;
171//! type Suspend = Returned<Blastoff>;
172//! fn resume(self, _: ()) -> Self::Suspend {
173//! Returned(Blastoff)
174//! }
175//! }
176//!
177//! let countdown = Three;
178//! let Yielded(n, countdown) = countdown.resume(());
179//! println!("{}", n);
180//! let Yielded(n, countdown) = countdown.resume(());
181//! println!("{}", n);
182//! let Yielded(n, countdown) = countdown.resume(());
183//! println!("{}", n);
184//! let Returned(blastoff) = countdown.resume(());
185//! println!("{:?}!", blastoff);
186//! ```
187//!
188//! This shows how the `Next` associated type can be used to chain together
189//! coroutines of different types, as long as they all have the same input and
190//! output types (`Y`, `R`, and `I`).
191//!
192//! The `as_yield()` and `as_return()` helper methods on `Suspended` provide a
193//! convenient way to get an `Option` of the yielded value or the return value,
194//! respectively.
195//!
196//! Another thing to note: the `Void` coroutine can be used as the `Next` type
197//! to statically indicate that the coroutine will not yield again. Because the
198//! `Void` type is not instantiable, it is impossible to `Yield` from a
199//! coroutine whose `Next` type is `Void`.
200//!
201//! One could have written the same example using closures:
202//!
203//! ```rust
204//! use cocoro::{from_fn, Coro, Returned, Suspended, Void, Yielded};
205//! #[derive(Debug, PartialEq, Eq)]
206//! struct Blastoff;
207//! #[rustfmt::skip]
208//! let countdown = from_fn(|_| {
209//! Yielded(3, from_fn(|_| {
210//! Yielded(2, from_fn(|_| {
211//! Yielded(1, from_fn(|_| {
212//! Returned(Blastoff) })) })) }))
213//! });
214//! let Yielded(n, countdown) = countdown.resume(());
215//! println!("{}", n);
216//! let Yielded(n, countdown) = countdown.resume(());
217//! println!("{}", n);
218//! let Yielded(n, countdown) = countdown.resume(());
219//! println!("{}", n);
220//! let Returned(blastoff) = countdown.resume(());
221//! println!("{:?}!", blastoff);
222//! ```
223//!
224//! This is considerably more compact, but formatting it the way that rustfmt
225//! wants can look very unapproachable. Nevertheless, this example shows that
226//! you can define coroutines with a static, type-safe state machine from
227//! ordinary closures and proves that this state information is preserved at
228//! compile time with irrefutable pattern matching.
229//!
230//! More commonly, you will use the `Suspend` enum, rather than `Yielded` or
231//! `Returned` structs, in `from_fn()` coroutines, especially if the coroutine
232//! uses run-time information to determine whether to yield or return.
233//!
234//! # Theory
235//!
236//! The main motivation of this crate is to show how the Rust type system could
237//! provide compile-time correct-by-construction guarantees that standard
238//! library coroutines, or other state machines like `Iterator` and `Future`,
239//! imply by contract but cannot enforce at compile time: they will never yield
240//! again after returning. Most design choices follow from that, including,
241//! ultimately, the emphasis on functional combinators, which are helpful to add
242//! the missing expressivity that can't come from procedural `gen` blocks. `gen`
243//! blocks are the reason for `Pin`, which would have, if used in `cocoro`
244//! coroutines, forced a `resume()` method to mutably borrow a (pinned)
245//! reference rather than accept `self` by value, which was the only way to
246//! "maybe consume" the coroutine, passing it back to the caller on yield but
247//! dropping it on return.
248//!
249//! Because `cocoro` coroutines are implemented with combinators and manual
250//! impls instead of syntactic sugar like `gen` blocks or macros that transform
251//! code, the trait was designed to be interoperable with other traits and use
252//! functional programming patterns make them as expressive as their procedural
253//! counterparts.
254//!
255//! A `cocoro` coroutine is a functor over both the yielded type and the return
256//! type. The `map_yield` and `map_return` combinators correspond to the
257//! theoretical `map` operation on these respective interpretations of the
258//! coroutine as a functor.
259//!
260//! A coroutine is also a *contravariant functor* over the input type, so you
261//! can `contramap_input()` with a function that takes a different input type
262//! and returns the original input type, and get a new coroutine that uses the
263//! `contramap()` function's input type as its input type.
264//!
265//! The `flatten()` combinator corresponds to the `join` operation on the monad
266//! over the return type. With it and `map_return()`, the `flat_map()`
267//! combinator can be implemented, corresponding to the `bind` operation on the
268//! monad.
269//!
270//! In order to complete the monad axioms, the `return` operation is implemented
271//! with the `JustReturn` wrapper struct, which is a coroutine that can take
272//! anything as an input, never yields, and always returns with the value it was
273//! constructed with. Together, the `JustReturn` struct and `flat_map()`
274//! combinator abide by the monad laws for the functor over the return type.
275//!
276//! There is no monad implementation for the functor over the yielded type, but
277//! the `just_yield` struct can be thought of as a `pure` operation for an
278//! *applicative functor* over the yielded type. The `zip()` combinator
279//! meanwhile is an operation on which the applicative `lifta2` function can be
280//! derived.
281//!
282//! Formalizing operations on coroutines as well-established functional
283//! programming concepts is a way to show that the `cocoro` coroutines are
284//! theoretically sound and can be used in a variety of ways that are familiar
285//! to functional programmers. Which includes Rust programmers who are familiar
286//! with other types with similar traits, like `Iterator` and `Result`.
287//!
288//! # FAQ
289//!
290//! ## Why is it called `cocoro`?
291//!
292//! I was thinking more along the lines of a pun: "coro" as an abbreviation
293//! for "coroutine", and "co" as a prefix meaning "together" or "with" to indicate
294//! its complementarity and perhaps subordination to Rust standard library
295//! coroutines. As another layer to the pun, "cocoro" sounds like "kokoro",
296//! which is the Japanese word for "heart", with all the attendant connotations
297//! of mind, spirit, and core-ness.
298//!
299//! I was also vaguely gesturing at the idea of co- as a prefix for mathematical
300//! duals, especially in the context of category theory. Although a coroutine is
301//! not the categorical dual of a routine in any strict sense, one can entertain
302//! the concept of a "co-routine" as the dual of a "routine", and thus a
303//! co-coroutine as something... routine.
304//!
305//! But also the name happened to be free on crates.io.
306//!
307//! ## Why is the `resume()` method consuming `self`?
308//!
309//! In the `std::ops::Coroutine` trait, the `resume` method takes
310//! `Pin<&mut Self>` as the receiver type. This was done with the ability to
311//! write [`gen` blocks](https://github.com/rust-lang/rust/issues/117078) in
312//! mind, where the language would synthesize a state machine out of procedural
313//! code. These state machines could include local variables that are references
314//! to other local variables in the block, making the type is self-referential
315//! immovable after the references may have been created. To enforce this,
316//! generators require a `Pin<&mut Self>` to ensure that the generator is
317//! "pinned" in memory and cannot be moved. The same goes for the more
318//! well-known, and more stable, `Future` trait, for which self-referential
319//! implementations can be constructed with `async` blocks.
320//!
321//! The `cocoro` crate does not need to support self-referential types, because
322//! it does not try to describe the state machines of `gen` blocks. Instead,
323//! coroutines are hand-written or composed from combinators and closures that
324//! are not suspended.
325//!
326//! ## Ok, but why not use `&mut self` as the reciver for `resume()`?
327//!
328//! This is an experiment to try to express in the type system something that is
329//! implicit in the contract of types like `Iterator`, `Future`, and, yes,
330//! `std::ops::Coroutine`. The contract is that once you hit the "end" of the
331//! iterator/future/coroutine (i.e. it returns `None`/`Ready`/`Complete`), you
332//! can't call `next()`/`poll()`/`resume()` again. Some utility functions like
333//! `Iterator::fuse()` attempt to add a runtime safety layer to more concretely
334//! define what happens when the contract is violated.
335//!
336//! But `cocoro` chooses a different approach: enforce this contract at compile
337//! time. The `resume()` method consumes `self` and returns a `Suspended` enum
338//! that can only obtain the coroutine back if it `Yield`s. This makes it
339//! impossible to call `resume()` again after a `Return`, because the `Return`
340//! variant does not include a coroutine to resume, and calling `resume()`
341//! had already consumed the coroutine.
342//!
343//! ## How's the performance compared to `std::ops::Coroutine`?
344//!
345//! The `cocoro` coroutines are designed to be as lightweight as possible. But
346//! so are the `std::ops::Coroutine` coroutines, and by people who are much more
347//! dedicated and experienced in writing high-performance Rust code.
348//!
349//! `cocoro` coroutines are "stackless" and avoid allocation, just like
350//! `std::ops::Coroutine` coroutines. One potential benefit of `cocoro`
351//! coroutines is that standard library coroutines are desugared into state
352//! machines that may be represented as a Rust enum (see the [unstable book](
353//! https://doc.rust-lang.org/beta/unstable-book/language-features/coroutines.html#coroutines-as-state-machines))
354//! which means that every iteration of the state machine may contain a branch.
355//! `cocoro` coroutines, on the other hand, support coroutines that are
356//! statically known to proceed deterministically to a particular state, as
357//! seen with the `just_yield()` and `just_return()` functions. Such coroutines
358//! eliminate branches at these points, which may lead to faster code.
359//!
360//! However, `cocoro` coroutines move the data that represents their state
361//! around when resumed and suspended, as opposed to standard library
362//! coroutines, which must be pinned in memory. This means there may be copy
363//! operations in the code generated by `cocoro` coroutines that don't appear
364//! in the code generated by standard library coroutines. I generally expect the
365//! compiler to optimize these copies away, but I haven't done any benchmarks to
366//! confirm this.
367//!
368//! Many basic `cocoro` combinators are tail recursive and can be optimized by
369//! the compiler into loops, but Rust does not guarantee tail call optimization
370//! in general.
371//!
372//! ## Why return an associated type instead of a `Suspend` enum?
373//!
374//! This is the secret sauce that allows `cocoro` coroutines to opt into
375//! statically deterministic state machines. The `Next` associated type of a
376//! coroutine is the type of the next state of the coroutine, and if every
377//! coroutine were to neecessarily return an enum, the compiler would have to
378//! insert a check on that enum's tag when matching the result.
379//!
380//! In particular, this lets `just_yield()` coroutines return a `Yielded` struct
381//! instead of a `Suspend` enum's `Yield` variant, and `just_return()`
382//! coroutines return a `Returned` struct instead of a `Suspend` enum's
383//! `Return` variant. All three of `Suspend`, `Yielded`, and `Return` implement
384//! the `Suspended` trait, so they are all valid options to return from a
385//! coroutine depending on whether it is known to branch at runtime.
386//!
387//! ## Why are the yield and return types generic type parameters?
388//!
389//! It's an established convention in Rust that "input" types for a trait are
390//! generic types, and "output" types are associated types, as seen in the
391//! `FnOnce` trait. It's important that traits like this can be generic over
392//! input types, i.e. implement the trait for arbitrarily many possible
393//! input types. This is what allows the `Fn` series of traits to work with
394//! reference types, which are actually higher-ranked types that are generic
395//! over an elided lifetime parameter.
396//!
397//! The `Coro` trait is generic over not just the input type, but the yield and
398//! return types too. This is to allow a type that implements `Coro` to
399//! implement the trait for arbitrarily many input, yield, and return types.
400//! For example, a coroutine that ignores its input can be generic over all
401//! `I` input types, a coroutine that never yields can be generic over all `Y`
402//! yield types, and a coroutine that never returns can be generic over all `R`
403//! return types.
404//!
405//! When you use `just_yield()` to make a coroutine, you can inject that
406//! coroutine into any function that takes a coroutine with any return type,
407//! or return it from a function that uses `impl Coro` return type syntax with
408//! any return type.
409//!
410//! This does mean that type annotations need to be provided in some places that
411//! otherwise wouldn't need them if there were `Yield` and `Return` associated
412//! types. You'll see `.yields::<Void>()` and `.returns::<Void>()` a lot in
413//! examples to provide required type annotations for code snippets contained
414//! within a single function's body. But in real code, you will likely be
415//! returning or consuming coroutines through functions that have `impl Coro`
416//! bounds that specify the expected yield, return, and input types, which
417//! guide the type checker to select one particular implementation of the `Coro`
418//! trait out of infinitely many.
419
420// Coroutines should not rely on any allocators or system calls.
421//
422// Tests for integrations with standard library APIs that use these features
423// should be put in the integratation tests in the `tests/` folder.
424#![no_std]
425
426mod compose;
427mod contramap_input;
428mod coro;
429mod either;
430mod fixed_point;
431mod flatten;
432mod from_fn;
433mod into_coro;
434mod just_return;
435mod just_yield;
436mod map_return;
437mod map_yield;
438mod map_yield_while;
439mod metaprogramming;
440mod recursive;
441mod suspend;
442mod suspended;
443mod void;
444mod yield_with;
445mod zip;
446
447pub use coro::Coro;
448pub use fixed_point::FixedPointCoro;
449pub use from_fn::from_fn;
450pub use into_coro::IntoCoro;
451pub use just_return::just_return;
452pub use just_return::Returned;
453pub use just_yield::just_yield;
454pub use just_yield::Yielded;
455pub use recursive::recursive;
456pub use suspend::Suspend;
457pub use suspended::Suspended;
458pub use void::Void;
459pub use yield_with::yield_with;
460
461/// `Yield` and `Return` are imported into the crate root namespace because
462/// they are used so often. Do not confuse these enum variants with the
463/// `Yielded` and `Returned` structs.
464pub use Suspend::{Return, Yield};
465
466#[cfg(test)]
467mod test;