par/lib.rs
1//! **What's a session type, anyway?** It's a description an entire external behavior
2//! of a concurrent, message-passing process. From the first message, through every
3//! possible path and interaction that can be made with it, to all the ways it can
4//! finish.
5//!
6//! When implementing a concurrent program according to a session type, the type tells
7//! what can happen at any point in the program. When we have to send a message, when to
8//! select a path to continue, when to wait for someone else to make a choice and adapt.
9//!
10//! Crucially, the types are designed to provide some useful guarantees:
11//!
12//! - **Protocol adherence** -- Expectations delivered, obligations fulfilled.
13//! - **Deadlock freedom** -- Cyclic communication is statically ruled out.
14//!
15//! _Protocol adherence_ means that when interacting with a process described by a session
16//! type, we can be sure (unless it crashes) that it will behave according to the protocol
17//! specified by its type. There will no unexpected messages, nor forgotten obligations.
18//! Just like we can rely on a function to return a string if it says so, we can rely on
19//! a process to send a string if its specified anywhere within its session type.
20//!
21//! _Deadlock freedom_ means that deadlocks can't happen, without dirty tricks anyway. It is
22//! achieved by imposing a certain structure on how processes are connected. It can take
23//! a bit of getting used to, but it becomes very important when implementing very complex
24//! concurrent systems.
25//!
26//! > ❓ Reading this, one may easily think, _"I don't see deadlocks happen in practice..."_,
27//! and that's a valid objection! But it arises from our concurrent systems not being very
28//! complex due to a lack of tools and types to design and implement them reliably.
29//! At high levels of complexity, deadlocks become an issue, and having them ruled out
30//! proves crucial.
31//!
32//! Using session types, complex concurrent systems can be modelled and implemented with confidence,
33//! as any type-checked program is guaranteed to adhere to its protocol, and avoid any deadlocks.
34//! Message passing itself ensures natural synchronization.
35//!
36//! Lastly, session types give names to concurrent concepts and patterns, which
37//! enables high levels of abstraction and composability. That makes it easier to
38//! reason and talk about large concurrent systems.
39//!
40//! > 📚 The particular flavor of session types presented here is a full implementation
41//! of propositional linear logic. However, no knowledge of linear logic is required to use
42//! or understand this library.
43//!
44//! # Forking
45//!
46//! Communication involves two opposing points of view. For example, an exchange
47//! of a message is seen as _sending_ on one side, and _receiving_ on the other. The idea
48//! extends to larger communication protocols. A turn-based game between two players looks
49//! like _"my move, your move"_ by one player, and _"your move, my move"_ by their opponent.
50//! Other examples are _"a player and the environment"_, _"a customer and a shop assistant"_,
51//! _"a server and a client"_.
52//!
53//! A type implementing the [`Session`] trait can be thought of as a handle to one of the
54//! two viewpoints of a certain communication protocol. Its associated type [Dual](Session::Dual)
55//! represents the other side, cleverly restricted as [`Session<Dual = Self>`](Session::Dual),
56//! so that the two types form a dual pair. **A concrete [`Session`] and its dual together
57//! describe a protocol.**
58//!
59//! For example, [`Recv<T>`](exchange::Recv) and [`Send<T>`](exchange::Send) from
60//! the [exchange] module form such a pair.
61//!
62//! A session handle can only be created together with its dual, and only in two independent
63//! visibility scopes. The former ensures protocol adherence -- the other side is always
64//! there --, while the latter prevents deadlocks -- different visibility scopes enable
65//! independent progress.
66//!
67//! This moment of creation is called *forking*. The [Session] trait has its
68//! [fork_sync](Session::fork_sync) method for this purpose.
69//!
70//! ```
71//! use par::{exchange::{Recv, Send}, Session};
72//!
73//! async fn forking() {
74//! let receiver: Recv<i64> = Recv::fork_sync(|sender: Send<i64>| {
75//! // scope of Send<i64>
76//! sender.send1(7);
77//! });
78//! // scope of Recv<i64>
79//! assert_eq!(receiver.recv1().await, 7);
80//! }
81//! ```
82//!
83//! The function passed to [fork_sync](Session::fork_sync) is not asynchronous and is
84//! fully executed before [fork_sync](Session::fork_sync) returns. Many useful combinators
85//! do not require `async`: offering only `async` forking would hurt those. Additionally,
86//! it enables the core library to be agnostic about the choice of an `async/await` runtime.
87//!
88//! However, in an application, we usually want the two sides to run concurrently. To do that,
89//! we spawn a coroutine/thread inside the synchronous function. For example, with Tokio:
90//!
91//! ```
92//! let sender: Send<i64> = Send::fork_sync(|receiver: Recv<i64>| {
93//! drop(tokio::spawn(async {
94//! assert_eq!(receiver.recv1().await, 7);
95//! }))
96//! });
97//! sender.send1(7);
98//! ```
99//!
100//! To make life easier, this crate provides utility modules for forking using popular runtimes.
101//! With these, the above can be reduced to:
102//!
103//! ```
104//! use par::runtimes::tokio::fork;
105//!
106//! let sender: Send<i64> = fork(|receiver: Recv<i64>| async {
107//! assert_eq!(receiver.recv1().await, 7);
108//! });
109//! sender.send1(7);
110//! ```
111//!
112//! > ❗️ Session end-points **must not be dropped.** (TODO: explain...)
113//!
114//! Now we will take a look at three basic ways to compose sessions:
115//! **sequencing**, **branching**, and **recursion**. These, together with
116//! [Recv](exchange::Recv) and [Send](exchange::Send), are enough to construct
117//! any complex session types (within the possibilities of the present framework). The
118//! other modules, [queue] and [server], merely standardize some (very useful) patterns.
119//!
120//! # Sequencing
121//!
122//! The two session types above, [Recv](exchange::Recv) and [Send](exchange::Send)
123//! actually take a second generic parameter: the continuation of the session. This
124//! continuation defaults to `()` if we leave it out.
125//!
126//! ```
127//! pub struct Recv<T, S: Session = ()> { /* private fields */ }
128//! pub struct Send<T, S: Session = ()> { /* private fields */ }
129//! ```
130//!
131//! The unit type `()` implements [Session] and represents an empty, finished session.
132//! It's self-dual, its dual is `()`.
133//!
134//! We can choose different continuations to make sequential sessions. For example:
135//!
136//! ```
137//! use par::{exchange::{Recv, Send}, Session};
138//!
139//! type Calculator = Send<i64, Send<Op, Send<i64, Recv<i64>>>>;
140//! enum Op { Plus, Times }
141//! ```
142//!
143//! Here we have a session which is sending two numbers and an operator, and finally
144//! receives a number back, presumably the result.
145//!
146//! To get a dual of a sequential session, we also have to dualize the continuation.
147//! In the end, we just flip every [Recv](exchange::Recv) to [Send](exchange::Send)
148//! and vice versa.
149//!
150//! The dual of the `Calculator` session is then
151//! `Recv<i64, Recv<Op, Recv<i64, Send<i64>>>>`.
152//!
153//! Here's one possible implementation:
154//!
155//! ```
156//! use par::{runtimes::tokio::fork, Dual};
157//!
158//! type User = Dual<Calculator>; // Recv<i64, Recv<Op, Recv<i64, Send<i64>>>>
159//!
160//! fn start_calculator() -> Calculator {
161//! fork(|user: User| async {
162//! let (x, user) = user.recv().await;
163//! let (op, user) = user.recv().await;
164//! let (y, user) = user.recv().await;
165//! let result = match op {
166//! Op::Plus => x + y,
167//! Op::Times => x * y,
168//! };
169//! user.send1(result);
170//! })
171//! }
172//!
173//! async fn calculate() {
174//! let sum = start_calculator()
175//! .send(3)
176//! .send(Op::Plus)
177//! .send(4)
178//! .recv1()
179//! .await;
180//!
181//! let product = start_calculator()
182//! .send(3)
183//! .send(Op::Times)
184//! .send(4)
185//! .recv1()
186//! .await;
187//!
188//! assert_eq!(sum, 7);
189//! assert_eq!(product, 12);
190//! }
191//! ```
192//!
193//! The type [`Dual<T>`] is just a convenient alias for `<T as Session>::Dual`.
194//!
195//! We use four different methods to communicate in the session:
196//!
197//! - For [Recv](exchange::Recv) it's [`recv`](exchange::Recv::recv) and
198//! [`recv1`](exchange::Recv::recv1), which need to be `.await`-ed.
199//! - For [Send](exchange::Send) it's [`send`](exchange::Send::send) and
200//! [`send1`](exchange::Send::send1), which are not `async`.
201//!
202//! The difference between [`recv`](exchange::Recv::recv) and
203//! [`recv1`](exchange::Recv::recv1), and between [`send`](exchange::Send::send)
204//! and [`send1`](exchange::Send::send1) is about the continuation.
205//! The versions ending with 1, [`recv1`](exchange::Recv::recv1) and
206//! [`send1`](exchange::Send::send1), can only be used if the continuation is `()`.
207//! Unlike their general versions, [`recv`](exchange::Recv::recv) and
208//! [`send`](exchange::Send::send), they don't return a continuation, and are
209//! not marked as `#[must_use]`.**
210//!
211//! The general [`recv`](exchange::Recv::recv) and [`send`](exchange::Send::send)
212//! can always be used instead of [`recv1`](exchange::Recv::recv1) and
213//! [`send1`](exchange::Send::send1), but then we have to deal with the returned
214//! `()`. The difference is just about convenience.
215//!
216//! # Branching
217//!
218//! Now that we know how to move forward, it's time to take a turn. Say we want to
219//! describe a communication between an ATM and a client interacting with its buttons.
220//! To make it simple, the interaction goes like this:
221//!
222//! 1. Client inserts a bank card.
223//! 2. If the card number is not valid, ATM rejects it and the session ends.
224//! 3. Otherwise, the ATM presents the client with two buttons:
225//! - **Check balance:** ATM shows the balance on the account and the session ends.
226//! - **Withdraw cash:** After pressing this button, the client enters the desired amount to withdraw.
227//! - If the amount is above the card's balance, ATM rejects and the session ends.
228//! - Otherwise all is good, the ATM outputs the desired amount and the session ends, too.
229//!
230//! To model such branching interaction, no additional dedicated types are provided by this crate.
231//! Instead, we make use of Rust's native enums, and the ability to send and receive not only values,
232//! but session as well.
233//!
234//! We start backwards, by modeling the interaction after an operation is selected:
235//!
236//! ```
237//! use par::exchange::{Recv, Send};
238//!
239//! struct Amount(i64);
240//! struct Money(i64);
241//! struct InsufficientFunds;
242//!
243//! enum Operation {
244//! CheckBalance(Send<Amount>),
245//! Withdraw(Recv<Amount, Send<Result<Money, InsufficientFunds>>>),
246//! }
247//! ```
248//!
249//! The helper types -- `Amount`, `Money`, and `InsufficientFunds` -- are just to aid readability.
250//!
251//! When branching, the first thing to decide is who is choosing, and who is offering a choice. In this
252//! case, the client is choosing from two options offered by the ATM. In other words, the client will
253//! send a selected `Operation` to the ATM, which receives it.
254//!
255//! That's why the sessions exchanged are described from the ATM's point of view:
256//! - `Operation::CheckBalance` proceeds to send the account's balance to the client.
257//! - `Operation::Withdraw` starts by receiving an amount requested by the client, then goes on to
258//! either send cash to the client, or reject due to insufficient funds.
259//!
260//! Note, that there are already (kind of) two branching points above. One is between the two buttons,
261//! the second one is chosen by the ATM to accept or reject the requested amount. This second choice
262//! is only a "kind of" branching because no sessions are exchanged. But, an equivalent way to encode
263//! it would be to include a reception of the money on the client's side:
264//! `...Result<Recv<Money>, InsufficientFunds>...`.
265//!
266//! The beginning of the interaction then involves sending a selected `Operation` to the ATM, after
267//! validating the account's number:
268//!
269//! ```
270//! struct Account(String);
271//! struct InvalidAccount;
272//!
273//! type ATM = Send<Account, Recv<Result<Send<Operation>, InvalidAccount>>>;
274//! ```
275//!
276//! Here the session is described from the client's point of view. That's arbitrary choice -- the ATM's point
277//! of view is simply the dual of the above:
278//!
279//! ```
280//! use par::Dual;
281//!
282//! type Client = Dual<ATM>; // Recv<Account, Send<Result<Send<Operation>, InvalidAccount>>>
283//! ```
284//!
285//! ## Picking a choice with [`Send::choose`](exchange::Send::choose)
286//!
287//! Being able to model a session by freely using custom (`Operation`) or standard ([`Result`]) enums
288//! is certainly expressive, but how ergonomic is it to use? Turns out it can be quite ergonomic thanks to
289//! the [`choose`](exchange::Send::choose) method on [`Send`](exchange::Send).
290//!
291//! Let's implement a client that checks the balance on their account. Here's a function that, given an
292//! account number, starts a `Client` session which does exactly that:
293//!
294//! ```
295//! use par::runtimes::tokio::fork;
296//!
297//! fn check_balance(number: String) -> Client {
298//! fork(|atm: ATM| async move {
299//! let atm = atm.send(Account(number.clone()));
300//! let Ok(atm) = atm.recv1().await else {
301//! return println!("Invalid account: {}", number);
302//! };
303//! let Amount(funds) = atm.choose(Operation::CheckBalance).recv1().await;
304//! println!("{} has {}", number, funds);
305//! })
306//! }
307//! ```
308//!
309//! After sending the account number and receiving a positive response from the ATM, the client is
310//! presented with a choice of the operation: _check balance_ or _withdraw money_.
311//!
312//! ```
313//! let Amount(funds) = atm.choose(Operation::CheckBalance).recv1().await;
314//! ```
315//!
316//! At this point, the type of `atm` is `Send<Operation>`. But instead of calling [`send1`](exchange::Send::send1),
317//! we [choose](exchange::Send::choose) `Operation::CheckBalance`.
318//!
319//! Recall that the payload of `Operation::CheckBalance` is `Send<Amount>`. [`Send::choose`](exchange::Send::choose)
320//! returns the **dual of that payload!**
321//!
322//! ```
323//! atm.choose(Operation::CheckBalance) // -> Recv<Amount>
324//! .recv1().await;
325//! ```
326//!
327//! That's why we can simply receive the response from the ATM afterwards.
328//!
329//! **What's going on?**
330//!
331//! There are two manual ways to accomplish what [`choose`](exchange::Send::choose) does.
332//!
333//! 1. The **inside** way:
334//!
335//! ```
336//! atm.send1(Operation::CheckBalance(fork(|atm: Recv<Amount>| async move {
337//! let Amount(funds) = atm.recv1().await;
338//! println!("{} has {}", number, funds);
339//! })));
340//! ```
341//!
342//! 2. The **outside** way:
343//!
344//! ```
345//! let atm: Recv<Amount> = fork(|client: Send<Amount>| async move {
346//! atm.send1(Operation::CheckBalance(client));
347//! });
348//! let Amount(funds) = atm.recv1().await;
349//! println!("{} has {}", number, funds);
350//! ```
351//!
352//! Which can also be written with [`fork_sync`](Session::fork_sync)!
353//!
354//! ```
355//! let atm = <Recv<Amount>>::fork_sync(|client: Send<Amount>| {
356//! atm.send1(Operation::CheckBalance(client))
357//! });
358//! let Amount(funds) = atm.recv1().await;
359//! println!("{} has {}", number, funds);
360//! ```
361//!
362//! The inside way introduces nesting of the follow-up code which the outside way avoids. Since
363//! avoiding nesting is beneficial enough to warrant (validly) the whole async/await paradigm
364//! (replacing nested callbacks), the **outside** way is superior.
365//!
366//! In short, all [`choose`](exchange::Send::choose) does is codify this outside form into a method.
367//! With any `Enum` and its `Enum::Variant`,
368//!
369//! ```
370//! let session = Session::fork_sync(|dual| session.send1(Enum::Variant(dual)));
371//! ```
372//!
373//! becomes
374//!
375//! ```
376//! let session = session.choose(Enum::Variant);
377//! ```
378//!
379//! Without any additional explanation, here's a possible implementation of a client withdrawing money.
380//!
381//! ```
382//! fn withdraw(number: String, Amount(requested): Amount) -> Client {
383//! fork(|atm: ATM| async move {
384//! let Ok(atm) = atm.send(Account(number.clone())).recv1().await else {
385//! return println!("Invalid account: {}", number);
386//! };
387//! let response = atm
388//! .choose(Operation::Withdraw)
389//! .send(Amount(requested))
390//! .recv1()
391//! .await;
392//! match response {
393//! Ok(Money(withdrawn)) => println!("{} withdrawn from {}", withdrawn, number),
394//! Err(InsufficientFunds) => println!(
395//! "{} has insufficient funds to withdraw {}",
396//! number, requested
397//! ),
398//! }
399//! })
400//! }
401//! ```
402//!
403//! # Linking
404//!
405//! In the last example, we defined dual sessions `ATM` and `Client` and implemented two behaviors
406//! for `Client`.
407//!
408//! ```
409//! fn check_balance(number: String) -> Client { /* ... */ }
410//! fn withdraw(number: String, Amount(requested): Amount) -> Client { /* ... */ }
411//! ```
412//!
413//! For a full program, we're missing the `ATM`'s side. We could just take the returned `Client`s
414//! and interact with them directly, but we can also construct an `ATM` separately. Then the question
415//! becomes how to **wire them together.**
416//!
417//! To show that, we first need an `ATM`. For simplicity, we'll be retrieving the accounts from a
418//! `HashMap<String, Money>`, without updating their balances upon withdrawals. Implementing that is
419//! left as a simple optional exercise for the reader.
420//!
421//! Understanding the implementation below is not important for this section. All that's important is
422//! to know the `ATM` looks in the provided `accounts`, and responds depending on the existence of a
423//! requested account and its balance.
424//!
425//! ```
426//! use std::collections::HashMap;
427//! use std::sync::Arc;
428//!
429//! fn boot_atm(accounts: Arc<HashMap<String, Money>>) -> ATM {
430//! fork(|client: Client| async move {
431//! let (Account(number), client) = client.recv().await;
432//! let Some(&Money(funds)) = accounts.get(&number) else {
433//! return client.send1(Err(InvalidAccount));
434//! };
435//! match client.choose(Ok).recv1().await {
436//! Operation::CheckBalance(client) => client.send1(Amount(funds)),
437//! Operation::Withdraw(client) => {
438//! let (Amount(requested), client) = client.recv().await;
439//! if funds >= requested {
440//! client.send1(Ok(Money(requested)));
441//! } else {
442//! client.send1(Err(InsufficientFunds));
443//! }
444//! }
445//! }
446//! })
447//! }
448//! ```
449//!
450//! Let's boot the ATM!
451//!
452//! ```
453//! let accounts = Arc::new(HashMap::from([
454//! ("Alice".to_string(), Money(1000)),
455//! ("Bob".to_string(), Money(700)),
456//! ("Cyril".to_string(), Money(5500)),
457//! ]));
458//!
459//! let atm = boot_atm(Arc::clone(&accounts));
460//! ```
461//!
462//! Then start a `Client` session to withdraw some money from Cyril's account.
463//!
464//! ```
465//! let client = withdraw("Cyril".to_string(), Amount(2500));
466//! ```
467//!
468//! The two dual sessions are now up and running. For wiring them together, the [Session] trait
469//! provides a useful method: [`Session::link`]!
470//!
471//! Like [`send`](exchange::Send::send), it is **non-blocking** and **non-async**: it tells the
472//! two sessions to talk to each other and immediately proceeds, no `.await` required. Here's what
473//! it looks like:
474//!
475//! ```
476//! use par::Session;
477//!
478//! atm.link(client);
479//! ```
480//!
481//! And the whole program:
482//!
483//! ```
484//! use par::Session;
485//!
486//! #[tokio::main]
487//! async fn main() {
488//! let accounts = Arc::new(HashMap::from([
489//! ("Alice".to_string(), Money(1000)),
490//! ("Bob".to_string(), Money(700)),
491//! ("Cyril".to_string(), Money(5500)),
492//! ]));
493//!
494//! let atm = boot_atm(Arc::clone(&accounts));
495//! let client = withdraw("Cyril".to_string(), Amount(2500));
496//!
497//! atm.link(client);
498//!
499//! // atm and client talk in the background, let them speak
500//! tokio::time::sleep(Duration::from_secs(1)).await;
501//! }
502//! ```
503//!
504//! **A caveat of non-blocking functions** like [`send`](exchange::Send::send) or [`link`](Session::link)
505//! is we need to add extra synchronization if we need to wait for the outcome. This usually isn't a concern when
506//! a program is well intertwined -- which it usually is. In this case, though, we need to wait for the two
507//! parties to finish interacting before exiting. We could insert an auxiliary [`Recv`](exchange::Recv), but once
508//! again, for simplicity, we just sleep.
509//!
510//! ```plain
511//! 2500 withdrawn from Cyril
512//! ```
513//!
514//! Linking is like a generalized calling of a function. It can be done on a single line, even if it's a little
515//! verbose in Rust.
516//!
517//! ```
518//! #[tokio::main]
519//! async fn main() {
520//! let accounts = Arc::new(HashMap::from([
521//! ("Alice".to_string(), Money(1000)),
522//! ("Bob".to_string(), Money(700)),
523//! ("Cyril".to_string(), Money(5500)),
524//! ]));
525//!
526//! boot_atm(Arc::clone(&accounts)).link(check_balance("Alice".to_string()));
527//! boot_atm(Arc::clone(&accounts)).link(withdraw("Bob".to_string(), Amount(1000)));
528//! boot_atm(Arc::clone(&accounts)).link(withdraw("Dylan".to_string(), Amount(20)));
529//!
530//! tokio::time::sleep(Duration::from_secs(1)).await;
531//! }
532//! ```
533//!
534//! ```plain
535//! Bob has insufficient funds to withdraw 1000
536//! Alice has 1000
537//! Invalid account: Dylan
538//! ```
539//!
540//! # Recursion
541//!
542//! In the realm of algebraic types, the basic building blocks of products and sums (`struct`s and `enum`s
543//! in Rust) **explode** into lists, maps, stacks, queues, and all kinds of other powerful data structures --
544//! **via recursion.** The same happens in the realm of session types: the basic building blocks of **sequencing
545//! and branching** make processing pipelines, worker pools, servers, game rule protocols, and so much more, when
546//! combined recursively.
547//!
548//! Now that we've covered those basic building blocks, let's take a look at how to create recursive session
549//! types to define complex and intricate communication protocols.
550//!
551//! To start, we'll stay with two-party protocols, but in the next section, we'll also demonstrate how to
552//! construct sessions intertwining **more than two parties** (/ agents / processes / threads).
553//!
554//! **One of the most common tasks** that involve repeating something an unknown number of times is **processing
555//! a stream of incoming data.** The [queue] module implements dedicated session types for this purpose. Before
556//! taking a look at it, though, we'll implement such a protocol manually, to see how it's done.
557//!
558//! **The task is:** There is an incoming stream of integers. Add them all up and report the total sum back.
559//!
560//! A recursive session protocol is exactly what we need to accomplish this. First it needs to branch on
561//!
562//! 1. **receiving a number to add**, or
563//! 2. **finishing and reporting the total**.
564//!
565//! Then, in the first case, it needs to go back and repeat, until eventually reaching the second case.
566//!
567//! Native recursion on types in Rust is all we need:
568//!
569//! ```
570//! enum Counting {
571//! More(Recv<i64, Recv<Counting>>),
572//! Done(Send<i64>),
573//! }
574//! ```
575//!
576//! The `enum` defines two variants. On `Counting::More`, the counter receives a number and continues
577//! recursively. On `Counting::Done`, it's required to send a number back: the total.
578//!
579//! A couple of things to note:
580//! - `Counting` itself is not a session type, just an `enum`. The two sides of the session will be
581//! using `Recv<Counting>` (from the counter's point of view) and `Send<Counting>`. In more complicated
582//! use-cases, it's recommended to set type aliases for the respective `Recv<...>` and `Send<...>` sides.
583//! - No `Box` or `Arc` is required at the recursion point, `Counting` is [`Sized`]. That's because the
584//! memory indirection needed for recursive types is already taken care of by the channels used in
585//! [`Recv`](exchange::Recv)/[`Send`](exchange::Send).
586//!
587//! While the session type is recursive, its implementation doesn't have to be! In fact, we'll implement
588//! the counter using a loop and re-assigning:
589//!
590//! ```
591//! fn start_counting() -> Send<Counting> {
592//! fork(|mut numbers: Recv<Counting>| async {
593//! let mut total = 0;
594//! loop {
595//! match numbers.recv1().await {
596//! Counting::More(number) => {
597//! let (n, next) = number.recv().await;
598//! total += n;
599//! numbers = next;
600//! }
601//! Counting::Done(report) => break report.send1(total),
602//! }
603//! }
604//! })
605//! }
606//! ```
607//!
608//! The counter's end-point of the session (`numbers`) is marked `mut`. In the case of `Counting::More`, after
609//! receiving the `n` to add and the `next` continuation of the session, we simply re-assign `next` into `numbers`.
610//! Note, that before the re-assignment, `numbers` has been moved-out-of in `numbers.recv1().await` -- no dropping
611//! of a session happens.
612//!
613//! Here's how we can use the constructed counter to add up numbers between 1 and 5:
614//!
615//! ```
616//! let sum = start_counting()
617//! .choose(Counting::More).send(1)
618//! .choose(Counting::More).send(2)
619//! .choose(Counting::More).send(3)
620//! .choose(Counting::More).send(4)
621//! .choose(Counting::More).send(5)
622//! .choose(Counting::Done).recv1().await;
623//!
624//! assert_eq!(sum, 15);
625//! ```
626//!
627//! The pattern of processing an incoming stream of data is **ubiquitous enough to warrant standardization.** That's
628//! what the [queue] module is. Check out its documentation for more detail! It provides two ends of a stream
629//! processing queue -- [`Dequeue`](queue::Dequeue) and [`Enqueue`](queue::Enqueue) -- corresponding to the
630//! `Recv<Counting>` and `Send<Counting>`, respectively. Instead of `Counting::More` and `Counting::Done`, we can use
631//! [`Enqueue::push`](queue::Enqueue::push) and [`Enqueue::close`](queue::Enqueue::close). On the processing side,
632//! [`Dequeue::pop`](queue::Dequeue::pop), [`Dequeue::for_each`](queue::Dequeue::for_each), and
633//! [`Dequeue::fold`](queue::Dequeue::fold) are provided for ergonomic use.
634//!
635//! Without further explanation, the counter can be rewritten this way:
636//!
637//! ```
638//! type Numbers = Dequeue<i64, Send<i64>>;
639//! type Counter = Dual<Numbers>; // Enqueue<i64, Recv<i64>>
640//!
641//! fn start_counting_with_queue() -> Counter {
642//! fork(|numbers: Numbers| async {
643//! let (total, report) = numbers
644//! .fold(0, |total, add| async move { total + add })
645//! .await;
646//! report.send1(total);
647//! })
648//! }
649//! ```
650//!
651//! And used elegantly:
652//!
653//! ```
654//! let sum = start_counting_with_queue()
655//! .push(1)
656//! .push(2)
657//! .push(3)
658//! .push(4)
659//! .push(5)
660//! .close()
661//! .recv1()
662//! .await;
663//!
664//! assert_eq!(sum, 15);
665//! ```
666//!
667//! # Multiple participants
668//!
669//! All session types described so far had two sides to them: an ATM versus a single client; a calculator, or
670//! a counter versus a single user. That's rarely sufficient for reasonable applications. A server will handle
671//! multiple clients, a game will have multiple players interacting in its world.
672//!
673//! Those familiar with literature will know that our session types here are _binary_. They fundamentally
674//! have two sides of communication. The other side is always described by the dual. Research in session types
675//! goes on to introduce _multi-party session types_ that explicitly model communication of more than two
676//! sides. While they have their advantages, they also introduce complexity and (_disclaimer: personal impression_)
677//! don't seem to be ready for practical use.
678//!
679//! What we are going to show here, however, is that **no dedicated multi-party session types are needed** to
680//! model and implement protocols involving arbitrary numbers of participants. And that is **without any concessions
681//! to previously described guarantees** like protocol adherence and deadlock freedom.
682//!
683//! The key lies in _how_ to **juggle multiple session end-points concurrently.**
684//!
685//! **To demonstrate,** we'll implement a very simple game of 3 players. For a general pattern of uniformly
686//! handling a dynamic number of participants, check out the [server] module.
687//!
688//! The rules of the game are:
689//!
690//! 1. Each player independently picks one of two moves: _UP_ or _DOWN_.
691//! 2. If everybody chooses the same move, it's a draw and the game repeats.
692//! 3. Otherwise, one player must have chosen differently from the other two: that player wins!
693//!
694//! In other words, the choices on the left map to the outcomes on the right:
695//!
696//! - _**UP**, DOWN, DOWN_, or _**DOWN**, UP, UP_ -- **first** player wins.
697//! - _DOWN, **UP**, DOWN_, or _UP, **DOWN**, UP_ -- **second** player wins.
698//! - _DOWN, DOWN, **UP**_, or _UP, UP, **DOWN**_ -- **third** player wins.
699//! - _UP, UP, UP_, or _DOWN, DOWN, DOWN_ -- **draw.**
700//!
701//! Let's model the game!
702//!
703//! ```
704//! #[derive(Debug)]
705//! enum Move {
706//! Up,
707//! Down,
708//! }
709//!
710//! enum Outcome {
711//! Win,
712//! Loss,
713//! Draw(Round),
714//! }
715//!
716//! type Round = Send<Move, Recv<Outcome>>;
717//! type Player = Dual<Round>; // Recv<Move, Send<Outcome>>
718//! ```
719//!
720//! The `Round` session is what the player sees. They are expected to send their move, then wait for an outcome
721//! of the round. Two outcomes are trivial: `Win` and `Loss`. The third one, `Draw`, recursively enters the next
722//! round, since no winner could be decided.
723//!
724//! The full game involves three players entering the game and playing according to the above protocol until
725//! one of them wins.
726//!
727//! ```
728//! #[derive(Debug)]
729//! enum Winner {
730//! First,
731//! Second,
732//! Third,
733//! }
734//!
735//! type Game = Send<(Player, Player, Player), Recv<Winner>>;
736//! ```
737//!
738//! We model it as a session that takes in three (running) `Player`s, and reports the winner back. In between
739//! those, an implementation of `Game` must take care of the three `Player` sessions by making them play.
740//!
741//! Here's what we can do:
742//!
743//! ```
744//! fn start_playing() -> Game {
745//! use {Move::*, Outcome::*, Winner::*};
746//!
747//! fork(|game: Dual<Game>| async {
748//! let ((mut player1, mut player2, mut player3), winner) = game.recv().await;
749//!
750//! loop {
751//! let (move1, outcome1) = player1.recv().await;
752//! let (move2, outcome2) = player2.recv().await;
753//! let (move3, outcome3) = player3.recv().await;
754//!
755//! tokio::time::sleep(Duration::from_secs(1)).await;
756//! println!("{:?} {:?} {:?}", move1, move2, move3);
757//! tokio::time::sleep(Duration::from_secs(1)).await;
758//!
759//! match (move1, move2, move3) {
760//! (Up, Down, Down) | (Down, Up, Up) => {
761//! outcome1.send1(Win);
762//! outcome2.send1(Loss);
763//! outcome3.send1(Loss);
764//! break winner.send1(First);
765//! }
766//! (Down, Up, Down) | (Up, Down, Up) => {
767//! outcome1.send1(Loss);
768//! outcome2.send1(Win);
769//! outcome3.send1(Loss);
770//! break winner.send1(Second);
771//! }
772//! (Down, Down, Up) | (Up, Up, Down) => {
773//! outcome1.send1(Loss);
774//! outcome2.send1(Loss);
775//! outcome3.send1(Win);
776//! break winner.send1(Third);
777//! }
778//! (Up, Up, Up) | (Down, Down, Down) => {
779//! player1 = outcome1.choose(Draw);
780//! player2 = outcome2.choose(Draw);
781//! player3 = outcome3.choose(Draw);
782//! println!("Draw...");
783//! }
784//! }
785//! }
786//! })
787//! }
788//! ```
789//!
790//! Let's break it down!
791//!
792//! At first, we take in the three `Player` session.
793//!
794//! ```
795//! let ((mut player1, mut player2, mut player3), winner) = game.recv().await;
796//! ```
797//!
798//! **All three of them are now in scope** at the same time. This is the key to handling multiple
799//! participants. Having access to all, the `Game` can **coordinate** them according to one another. In this
800//! context it's only to decide the outcome and proceed with the game -- in other contexts, more complicated
801//! mutual interactions can be implemented.
802//!
803//! The rest of the code is concerned with deciding the outcome and communicating it.
804//!
805//! - **In the case of a winner,** the `Player` sessions are terminated by either a `Win` or a `Loss`, and the
806//! _pending_ `winner` response is _completed_.
807//!
808//! - **Otherwise in the case of a draw,** the `winner` channel is _left pending_, and draws are communicated to
809//! the players, which makes them stay to play another round according to the `Round` protocol.
810//!
811//! **Let's have some fun playing!**
812//!
813//! ```
814//! fn random_player() -> Player {
815//! fork(|mut round: Round| async move {
816//! while let Outcome::Draw(next_round) = round.send(random_move()).recv1().await {
817//! round = next_round;
818//! }
819//! })
820//! }
821//!
822//! fn random_move() -> Move {
823//! if fastrand::bool() {
824//! Move::Up
825//! } else {
826//! Move::Down
827//! }
828//! }
829//!
830//! #[tokio::main]
831//! async fn main() {
832//! for _ in 0..10 {
833//! let winner = start_playing()
834//! .send((random_player(), random_player(), random_player()))
835//! .recv1()
836//! .await;
837//! println!("{:?}!\n", winner);
838//! }
839//! }
840//! ```
841//!
842//! ```plain
843//! Up Up Down
844//! Third!
845//!
846//! Down Up Up
847//! First!
848//!
849//! Down Down Down
850//! Draw...
851//! Up Down Up
852//! Second!
853//!
854//! Down Up Down
855//! Second!
856//! ```
857
858pub mod exchange;
859pub mod queue;
860pub mod runtimes;
861pub mod server;
862
863pub trait Session: Send + 'static {
864 type Dual: Session<Dual = Self>;
865
866 #[must_use]
867 fn fork_sync(f: impl FnOnce(Self::Dual)) -> Self;
868 fn link(self, dual: Self::Dual);
869}
870
871pub type Dual<S> = <S as Session>::Dual;
872
873impl Session for () {
874 type Dual = ();
875 fn fork_sync(f: impl FnOnce(Self::Dual)) -> Self {
876 f(())
877 }
878 fn link(self, (): Self::Dual) {}
879}