Session Types for Rust ---------------------- This is an implementation of [session types for Rust](http://munksgaard.me/papers/laumann-munksgaard-larsen.pdf). Using this library, you can implement **bi-directional process communication** with compile-time assurance that neither party will violate the communication protocol. [![Rust](https://github.com/Munksgaard/session-types/actions/workflows/rust.yml/badge.svg)](https://github.com/Munksgaard/session-types/actions/workflows/rust.yml) [![Cargo](https://img.shields.io/crates/v/session_types.svg)](https://crates.io/crates/session_types) [![Documentation](https://docs.rs/session_types/badge.svg)](https://docs.rs/session_types) ## Getting started [session-types is available on crates.io](https://crates.io/crates/session_types). It is recommended to look there for the newest released version, as well as links to the newest builds of the docs. At the point of the last update of this README, the latest published version could be used like this: Add the following dependency to your Cargo manifest... ```toml [dependencies] session_types = "0.3.1" ``` ...and see the [docs](https://docs.rs/session_types/) for how to use it. ## Example ```rust extern crate session_types; use session_types::*; use std::thread; type Server = Recv>; type Client = ::Dual; fn srv(c: Chan<(), Server>) { let (c, n) = c.recv(); if n % 2 == 0 { c.send(true).close() } else { c.send(false).close() } } fn cli(c: Chan<(), Client>) { let n = 42; let c = c.send(n); let (c, b) = c.recv(); if b { println!("{} is even", n); } else { println!("{} is odd", n); } c.close(); } fn main() { let (server_chan, client_chan) = session_channel(); let srv_t = thread::spawn(move || srv(server_chan)); let cli_t = thread::spawn(move || cli(client_chan)); let _ = (srv_t.join(), cli_t.join()); } ``` We start by specifying a _protocol_. Protocols are constructed from the protocol types `Send`, `Recv`, `Choose`, `Offer`, `Rec` and `Var` (see [Protocol types](#protocol-types)). In this case, our protocol is: ```rust type Server = Recv>; ``` which reads: * Receive a signed 64-bit integer * Send a boolean * Close the channel The `Client` protocol is the _dual_, which is a well-defined concept in session types. Loosely, it means that every protocol step in one process has a matching step in the other. If one process wants to send a value, the other process must be ready to receive it. The dual of `Server` is: ```rust type Client = Send>; ``` With `session-types`, we do not have to construct the dual by hand, as we leverage the trait system to model this concept with the `HasDual` trait. This allows us to do: ```rust type Client = ::Dual; ``` ## Why is it cool? Session types are not a new concept. They are cool, because they allow for _compile-time_ verification of process communication. In other words, we are able to check statically if two communicating processes adhere to their shared communication protocol. But implementing session types requires a way to model that certain actions in the protocol have taken place. This is a complicated thing to do statically in most programming languages, but in Rust it is easy because of _move semantics_. Using move semantics, we ensure that "taking a step" in the protocol (sending, receiving, etc) cannot be repeated. ## Protocol types Any session-typed communication protocol is constructed from some basic building blocks. This section goes through them pair-wise, showing an action and its dual. A session-typed channel is defined as `Chan` where `E` is an _environment_ and `P` is a protocol. The environment `E` is always `()` for newly created channels (ie it is empty). ### `Eps` This is the final step of any terminating protocol. The simplest example is: type Close = Eps; Any channel whose type is `Chan` implements the `close()` method that closes the connection. `Eps` is its own opposite, ie `::Dual = Eps` The simplest channels are of type `Chan<(), Eps>`. All you can do with such channels is to close the connection: let (a, b) = session_channel::(); a.close(); b.close(); ### `Send` and `Recv` These are the most basic of actions: Transmitting and receiving values. The opposite of a `Send` with some type `T` is a `Recv` of type `T`. type S = Send; type R = Recv; // ::Dual A channel of type `Chan>` implements the method `send(T) → Chan`. A channel of type `Chan>` implements the method `recv() → (T, Chan)`. ### `Choose` and `Offer` There is the option of making choices in the protocol, for one process to inform the other of a decision. The `Choose` and `Offer` constructs model such choices. type C = Choose, Recv>; type O = Offer, Send>; // ::Dual A channel of type `Chan>` implements _two_ methods: * `sel1() → Chan` * `sel2() → Chan` that communicates the choice of protocols `P` and `Q` to the other process. A channel of type `Chan>` implements `offer() → Branch, Chan>`. `Branch` is an enum defined as: enum Branch { Left(L), Right(R), } A call to `offer()` should then be matched on to figure out which path the other process decided to take. match c.offer() { Branch::Left(c) => …, Branch::Right(c) => …, } ### `Rec`, `Var`, `S` and `Z` The type `Rec` implements the ability the recurse in the protocol, ie provides an iterator component. The type `Rec

` allows repeating the protocol `P`. A channel of type `Chan>` implements the method `enter() → Chan<(P, E), P>`. Calling `enter()` "stores" the protocol `P` in the channel environment. The `Var` construct is then used to reference protocols stored in the environment. As the environment is essentially a stack, `Var` takes a counter that is modeled as a Peano number. `Var` points to the top of the stack. A channel of type `Chan<(P, E), Var>` implements the method `zero() → Chan<(P, E), P>`, ie `Var` is replaced by `P` at the top of the stack. type RS = Rec>>; type RR = Rec>>; // ::Dual The following program indefinitely sends some string: let c: Chan<(), Rec>> = …; let mut c = c.enter(); loop { c = c.send("Hello!".to_string()).zero(); } Protocols in the environment can also be popped from the stack with `Var>`. This requires there to be at least one protocol in the stack. A channel of type `Chan<(P, E), Var>>` implements the method `succ() -> Chan>` that peels away the `P` from the environment and `S` in the counter. ### Putting it all together Using the constructions from above allows us put together more complex protocols. For example: type Server = Rec< Offer< Eps, Recv>> > >; It reads: * In a loop, either: - Close the connection - Or: 1. Receive a `String` 2. Send back a `usize` 3. Go back to the beginning An example implementation: let c: Chan<(), Server> = …; let mut c = c.enter(); // c: Chan<(Offer<…>, ()), Offer<…>> loop { c = match c.offer() { Branch::Left(c) => { // c: Chan<(Offer<…>, ()), Eps> c.close(); break }, Branch::Right(c) => { // c: Chan<(Offer<…>, ()), Recv>>> let (c, str) = c.recv(); // c: Chan<(Offer<…>, ()), Send>> let c = c.send(str.len()); // c: Chan<(Offer<…>, ()), Var> c.zero() // c: Chan<(Offer<…>, ()), Offer<…>> } }; } ## Additional reading and examples For further information, check out [Session Types for Rust](http://munksgaard.me/papers/laumann-munksgaard-larsen.pdf) and the examples directory.