├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE.md ├── README.md ├── examples ├── arithmetic.rs ├── atm.rs ├── connect.rs ├── echo-server.rs ├── generic.rs ├── hselect.rs ├── many-clients.rs ├── planeclip.rs └── try-offer.rs ├── src └── lib.rs └── tests ├── chan_select.rs ├── compile-fail ├── cannot-impl-hasdual.rs ├── incompatible-sessions.rs ├── no-aliasing.rs └── send-on-recv.rs ├── drop_server.rs ├── send_recv.rs ├── tests.rs └── try_recv.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | 4 | pull_request: 5 | 6 | schedule: 7 | - cron: "0 0 * * 0" 8 | 9 | name: Rust 10 | 11 | jobs: 12 | check: 13 | name: Check 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout sources 17 | uses: actions/checkout@v2 18 | 19 | - name: Install stable toolchain 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | override: true 25 | 26 | - name: Run cargo check 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: check 30 | 31 | test: 32 | name: Test Suite 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout sources 36 | uses: actions/checkout@v2 37 | 38 | - name: Install stable toolchain 39 | uses: actions-rs/toolchain@v1 40 | with: 41 | profile: minimal 42 | toolchain: stable 43 | override: true 44 | 45 | - name: Run cargo test 46 | uses: actions-rs/cargo@v1 47 | with: 48 | command: test 49 | 50 | lints: 51 | name: Lints 52 | runs-on: ubuntu-latest 53 | steps: 54 | - name: Checkout sources 55 | uses: actions/checkout@v2 56 | 57 | - name: Install stable toolchain 58 | uses: actions-rs/toolchain@v1 59 | with: 60 | profile: minimal 61 | toolchain: stable 62 | override: true 63 | components: rustfmt, clippy 64 | 65 | - name: Run cargo fmt 66 | uses: actions-rs/cargo@v1 67 | with: 68 | command: fmt 69 | args: --all -- --check 70 | 71 | - name: Run cargo clippy 72 | uses: actions-rs/cargo@v1 73 | with: 74 | command: clippy 75 | args: -- -D warnings 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *~ 4 | 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "session_types" 3 | version = "0.3.1" 4 | authors = [ "Philip Munksgaard " 5 | , "Thomas Bracht Laumann Jespersen " 6 | ] 7 | 8 | description = "An implementation of session types in Rust" 9 | repository = "https://github.com/Munksgaard/session-types" 10 | readme = "README.md" 11 | keywords = ["session", "types", "channels", "concurrency", "communication"] 12 | 13 | license = "MIT" 14 | 15 | [dependencies] 16 | crossbeam-channel = "0.5.0" 17 | 18 | [dev-dependencies] 19 | compiletest_rs = { version = "0.6.0", features = ["stable"] } 20 | rand = "0.8.3" 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Thomas Bracht Laumann Jespersen, Philip Munksgaard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Session Types for Rust 2 | ---------------------- 3 | 4 | This is an implementation of [session types for Rust](http://munksgaard.me/papers/laumann-munksgaard-larsen.pdf). 5 | 6 | Using this library, you can implement **bi-directional process communication** 7 | with compile-time assurance that neither party will violate the communication 8 | protocol. 9 | 10 | [![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) 11 | 12 | ## Getting started 13 | 14 | [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. 15 | 16 | At the point of the last update of this README, the latest published version could be used like this: 17 | 18 | Add the following dependency to your Cargo manifest... 19 | 20 | ```toml 21 | [dependencies] 22 | session_types = "0.3.1" 23 | ``` 24 | 25 | ...and see the [docs](https://docs.rs/session_types/) for how to use it. 26 | 27 | ## Example 28 | 29 | ```rust 30 | extern crate session_types; 31 | use session_types::*; 32 | use std::thread; 33 | 34 | type Server = Recv>; 35 | type Client = ::Dual; 36 | 37 | fn srv(c: Chan<(), Server>) { 38 | let (c, n) = c.recv(); 39 | if n % 2 == 0 { 40 | c.send(true).close() 41 | } else { 42 | c.send(false).close() 43 | } 44 | } 45 | 46 | fn cli(c: Chan<(), Client>) { 47 | let n = 42; 48 | let c = c.send(n); 49 | let (c, b) = c.recv(); 50 | 51 | if b { 52 | println!("{} is even", n); 53 | } else { 54 | println!("{} is odd", n); 55 | } 56 | 57 | c.close(); 58 | } 59 | 60 | fn main() { 61 | let (server_chan, client_chan) = session_channel(); 62 | 63 | let srv_t = thread::spawn(move || srv(server_chan)); 64 | let cli_t = thread::spawn(move || cli(client_chan)); 65 | 66 | let _ = (srv_t.join(), cli_t.join()); 67 | } 68 | ``` 69 | 70 | We start by specifying a _protocol_. Protocols are constructed from the 71 | protocol types `Send`, `Recv`, `Choose`, `Offer`, `Rec` and `Var` (see 72 | [Protocol types](#protocol-types)). In this case, our protocol is: 73 | 74 | ```rust 75 | type Server = Recv>; 76 | ``` 77 | 78 | which reads: 79 | 80 | * Receive a signed 64-bit integer 81 | * Send a boolean 82 | * Close the channel 83 | 84 | The `Client` protocol is the _dual_, which is a well-defined concept in session 85 | types. Loosely, it means that every protocol step in one process has a matching 86 | step in the other. If one process wants to send a value, the other process must 87 | be ready to receive it. The dual of `Server` is: 88 | 89 | ```rust 90 | type Client = Send>; 91 | ``` 92 | 93 | With `session-types`, we do not have to construct the dual by hand, as we 94 | leverage the trait system to model this concept with the `HasDual` trait. This 95 | allows us to do: 96 | 97 | ```rust 98 | type Client = ::Dual; 99 | ``` 100 | 101 | ## Why is it cool? 102 | 103 | Session types are not a new concept. They are cool, because they allow for 104 | _compile-time_ verification of process communication. In other words, we are 105 | able to check statically if two communicating processes adhere to their shared 106 | communication protocol. 107 | 108 | But implementing session types requires a way to model that certain actions in 109 | the protocol have taken place. This is a complicated thing to do statically in 110 | most programming languages, but in Rust it is easy because of _move semantics_. 111 | 112 | Using move semantics, we ensure that "taking a step" in the protocol (sending, 113 | receiving, etc) cannot be repeated. 114 | 115 | ## Protocol types 116 | 117 | Any session-typed communication protocol is constructed from some basic 118 | building blocks. This section goes through them pair-wise, showing an action 119 | and its dual. 120 | 121 | A session-typed channel is defined as `Chan` where `E` is an 122 | _environment_ and `P` is a protocol. The environment `E` is always `()` for 123 | newly created channels (ie it is empty). 124 | 125 | ### `Eps` 126 | 127 | This is the final step of any terminating protocol. The simplest example is: 128 | 129 | type Close = Eps; 130 | 131 | Any channel whose type is `Chan` implements the `close()` method that 132 | closes the connection. `Eps` is its own opposite, ie `::Dual = Eps` 133 | 134 | The simplest channels are of type `Chan<(), Eps>`. All you can do with such 135 | channels is to close the connection: 136 | 137 | let (a, b) = session_channel::(); 138 | a.close(); 139 | b.close(); 140 | 141 | ### `Send` and `Recv` 142 | 143 | These are the most basic of actions: Transmitting and receiving values. The 144 | opposite of a `Send` with some type `T` is a `Recv` of type `T`. 145 | 146 | type S = Send; 147 | type R = Recv; // ::Dual 148 | 149 | A channel of type `Chan>` implements the method `send(T) → Chan`. 150 | 151 | A channel of type `Chan>` implements the method `recv() → (T, Chan)`. 152 | 153 | ### `Choose` and `Offer` 154 | 155 | There is the option of making choices in the protocol, for one process to 156 | inform the other of a decision. The `Choose` and `Offer` constructs model such 157 | choices. 158 | 159 | type C = Choose, Recv>; 160 | type O = Offer, Send>; // ::Dual 161 | 162 | A channel of type `Chan>` implements _two_ methods: 163 | 164 | * `sel1() → Chan` 165 | * `sel2() → Chan` 166 | 167 | that communicates the choice of protocols `P` and `Q` to the other process. 168 | 169 | A channel of type `Chan>` implements `offer() → Branch, Chan>`. `Branch` is an enum defined as: 171 | 172 | enum Branch { 173 | Left(L), 174 | Right(R), 175 | } 176 | 177 | A call to `offer()` should then be matched on to figure out which path the 178 | other process decided to take. 179 | 180 | match c.offer() { 181 | Branch::Left(c) => …, 182 | Branch::Right(c) => …, 183 | } 184 | 185 | ### `Rec`, `Var`, `S` and `Z` 186 | 187 | The type `Rec` implements the ability the recurse in the protocol, ie provides 188 | an iterator component. The type `Rec

` allows repeating the protocol `P`. 189 | 190 | A channel of type `Chan>` implements the method `enter() → Chan<(P, 191 | E), P>`. Calling `enter()` "stores" the protocol `P` in the channel 192 | environment. 193 | 194 | The `Var` construct is then used to reference protocols stored in the 195 | environment. As the environment is essentially a stack, `Var` takes a counter 196 | that is modeled as a Peano number. `Var` points to the top of the stack. 197 | 198 | A channel of type `Chan<(P, E), Var>` implements the method `zero() → 199 | Chan<(P, E), P>`, ie `Var` is replaced by `P` at the top of the stack. 200 | 201 | type RS = Rec>>; 202 | type RR = Rec>>; // ::Dual 203 | 204 | The following program indefinitely sends some string: 205 | 206 | let c: Chan<(), Rec>> = …; 207 | let mut c = c.enter(); 208 | loop { 209 | c = c.send("Hello!".to_string()).zero(); 210 | } 211 | 212 | Protocols in the environment can also be popped from the stack with 213 | `Var>`. This requires there to be at least one protocol in the stack. 214 | 215 | A channel of type `Chan<(P, E), Var>>` implements the method `succ() -> 216 | Chan>` that peels away the `P` from the environment and `S` in the 217 | counter. 218 | 219 | ### Putting it all together 220 | 221 | Using the constructions from above allows us put together more complex 222 | protocols. For example: 223 | 224 | type Server = Rec< 225 | Offer< 226 | Eps, 227 | Recv>> 228 | > 229 | >; 230 | 231 | It reads: 232 | 233 | * In a loop, either: 234 | - Close the connection 235 | - Or: 236 | 1. Receive a `String` 237 | 2. Send back a `usize` 238 | 3. Go back to the beginning 239 | 240 | An example implementation: 241 | 242 | let c: Chan<(), Server> = …; 243 | let mut c = c.enter(); // c: Chan<(Offer<…>, ()), Offer<…>> 244 | loop { 245 | c = match c.offer() { 246 | Branch::Left(c) => { // c: Chan<(Offer<…>, ()), Eps> 247 | c.close(); 248 | break 249 | }, 250 | Branch::Right(c) => { // c: Chan<(Offer<…>, ()), Recv>>> 251 | let (c, str) = c.recv(); // c: Chan<(Offer<…>, ()), Send>> 252 | let c = c.send(str.len()); // c: Chan<(Offer<…>, ()), Var> 253 | c.zero() // c: Chan<(Offer<…>, ()), Offer<…>> 254 | } 255 | }; 256 | } 257 | 258 | ## Additional reading and examples 259 | 260 | For further information, check out [Session Types for 261 | Rust](http://munksgaard.me/papers/laumann-munksgaard-larsen.pdf) and the 262 | examples directory. 263 | -------------------------------------------------------------------------------- /examples/arithmetic.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "cargo-clippy", allow(type_complexity))] 2 | // This is an implementation of the extended arithmetic server from 3 | // Vasconcelos-Gay-Ravara (2006) with some additional functionality 4 | 5 | extern crate session_types; 6 | use session_types::*; 7 | 8 | use std::thread::spawn; 9 | 10 | // Offers: Add, Negate, Sqrt, Eval 11 | type Srv = Offer< 12 | Eps, 13 | Offer< 14 | Recv>>>, 15 | Offer< 16 | Recv>>, 17 | Offer< 18 | Recv>, Var>>, 19 | Recv bool, Recv>>>, 20 | >, 21 | >, 22 | >, 23 | >; 24 | 25 | fn server(c: Chan<(), Rec>) { 26 | let mut c = c.enter(); 27 | loop { 28 | c = offer! { c, 29 | CLOSE => { 30 | c.close(); 31 | return 32 | }, 33 | ADD => { 34 | let (c, n) = c.recv(); 35 | let (c, m) = c.recv(); 36 | c.send(n + m).zero() 37 | }, 38 | NEGATE => { 39 | let (c, n) = c.recv(); 40 | c.send(-n).zero() 41 | }, 42 | SQRT => { 43 | let (c, x) = c.recv(); 44 | if x >= 0.0 { 45 | c.sel1().send(x.sqrt()).zero() 46 | } else { 47 | c.sel2().zero() 48 | } 49 | }, 50 | EVAL => { 51 | let (c, f) = c.recv(); 52 | let (c, n) = c.recv(); 53 | c.send(f(n)).zero() 54 | } 55 | } 56 | } 57 | } 58 | 59 | // `add_client`, `neg_client` and `sqrt_client` are all pretty straightforward 60 | // uses of session types, but they do showcase subtyping, recursion and how to 61 | // work the types in general. 62 | 63 | type AddCli = Choose>>>, R>>; 64 | 65 | fn add_client(c: Chan<(), Rec>>) { 66 | let (c, n) = c.enter().sel2().sel1().send(42).send(1).recv(); 67 | println!("{}", n); 68 | c.zero().sel1().close() 69 | } 70 | 71 | type NegCli = Choose>>, S>>>; 72 | 73 | fn neg_client(c: Chan<(), Rec>>) { 74 | let (c, n) = c.enter().skip2().sel1().send(42).recv(); 75 | println!("{}", n); 76 | c.zero().sel1().close(); 77 | } 78 | 79 | type SqrtCli = 80 | Choose>, Var>>, T>>>>; 81 | 82 | fn sqrt_client(c: Chan<(), Rec>>) { 83 | match c.enter().skip3().sel1().send(42.0).offer() { 84 | Left(c) => { 85 | let (c, n) = c.recv(); 86 | println!("{}", n); 87 | c.zero().sel1().close(); 88 | } 89 | Right(c) => { 90 | println!("Couldn't take square root!"); 91 | c.zero().sel1().close(); 92 | } 93 | } 94 | } 95 | 96 | // `fn_client` sends a function over the channel 97 | 98 | type PrimeCli = Choose< 99 | Eps, 100 | Choose bool, Send>>>>>>, 101 | >; 102 | 103 | fn fn_client(c: Chan<(), Rec>>) { 104 | fn even(n: i64) -> bool { 105 | n % 2 == 0 106 | } 107 | 108 | let (c, b) = c.enter().skip4().send(even).send(42).recv(); 109 | println!("{}", b); 110 | c.zero().sel1().close(); 111 | } 112 | 113 | // `ask_neg` and `get_neg` use delegation, that is, sending a channel over 114 | // another channel. 115 | 116 | // `ask_neg` selects the negation operation and sends an integer, whereafter it 117 | // sends the whole channel to `get_neg`. `get_neg` then receives the negated 118 | // integer and prints it. 119 | 120 | type AskNeg = Choose>>, S>>>; 121 | 122 | fn ask_neg( 123 | c1: Chan<(), Rec>>, 124 | c2: Chan<(), Send, ()), Recv>>, Eps>>, 125 | ) { 126 | let c1 = c1.enter().sel2().sel2().sel1().send(42); 127 | c2.send(c1).close(); 128 | } 129 | 130 | fn get_neg( 131 | c1: Chan<(), Recv, ()), Recv>>, Eps>>, 132 | ) { 133 | let (c1, c2) = c1.recv(); 134 | let (c2, n) = c2.recv(); 135 | println!("{}", n); 136 | c2.zero().sel1().close(); 137 | c1.close(); 138 | } 139 | 140 | fn main() { 141 | connect(server, add_client); 142 | connect(server, neg_client); 143 | connect(server, sqrt_client); 144 | connect(server, fn_client); 145 | 146 | let (c1, c1_) = session_channel(); 147 | let (c2, c2_) = session_channel(); 148 | 149 | let t1 = spawn(move || server(c1)); 150 | let t2 = spawn(move || ask_neg(c1_, c2)); 151 | let t3 = spawn(move || get_neg(c2_)); 152 | 153 | let _ = t1.join(); 154 | let _ = t2.join(); 155 | let _ = t3.join(); 156 | } 157 | -------------------------------------------------------------------------------- /examples/atm.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "cargo-clippy", allow(ptr_arg))] 2 | extern crate session_types; 3 | use session_types::*; 4 | use std::thread::spawn; 5 | 6 | type Id = String; 7 | type Atm = Recv, Eps>>; 8 | 9 | type AtmInner = Offer>>; 10 | 11 | type AtmDeposit = Recv>>; 12 | type AtmWithdraw = Recv, Var>>; 13 | type AtmBalance = Send>; 14 | 15 | type Client = ::Dual; 16 | 17 | fn approved(id: &Id) -> bool { 18 | !id.is_empty() 19 | } 20 | 21 | fn atm(c: Chan<(), Atm>) { 22 | let mut c = { 23 | let (c, id) = c.recv(); 24 | if !approved(&id) { 25 | c.sel2().close(); 26 | return; 27 | } 28 | c.sel1().enter() 29 | }; 30 | let mut balance = 0; 31 | loop { 32 | c = offer! { 33 | c, 34 | Deposit => { 35 | let (c, amt) = c.recv(); 36 | balance += amt; 37 | c.send(balance).zero() 38 | }, 39 | Withdraw => { 40 | let (c, amt) = c.recv(); 41 | if amt > balance { 42 | c.sel2().zero() 43 | } else { 44 | balance -= amt; 45 | c.sel1().zero() 46 | } 47 | }, 48 | Balance => { 49 | c.send(balance).zero() 50 | }, 51 | Quit => { 52 | c.close(); 53 | break 54 | } 55 | } 56 | } 57 | } 58 | 59 | fn deposit_client(c: Chan<(), Client>) { 60 | let c = match c.send("Deposit Client".to_string()).offer() { 61 | Left(c) => c.enter(), 62 | Right(_) => panic!("deposit_client: expected to be approved"), 63 | }; 64 | 65 | let (c, new_balance) = c.sel1().send(200).recv(); 66 | println!("deposit_client: new balance: {}", new_balance); 67 | c.zero().skip3().close(); 68 | } 69 | 70 | fn withdraw_client(c: Chan<(), Client>) { 71 | let c = match c.send("Withdraw Client".to_string()).offer() { 72 | Left(c) => c.enter(), 73 | Right(_) => panic!("withdraw_client: expected to be approved"), 74 | }; 75 | 76 | match c.sel2().sel1().send(100).offer() { 77 | Left(c) => { 78 | println!("withdraw_client: Successfully withdrew 100"); 79 | c.zero().skip3().close(); 80 | } 81 | Right(c) => { 82 | println!("withdraw_client: Could not withdraw. Depositing instead."); 83 | c.zero().sel1().send(50).recv().0.zero().skip3().close(); 84 | } 85 | } 86 | } 87 | 88 | fn main() { 89 | let (atm_chan, client_chan) = session_channel(); 90 | spawn(|| atm(atm_chan)); 91 | deposit_client(client_chan); 92 | 93 | let (atm_chan, client_chan) = session_channel(); 94 | spawn(|| atm(atm_chan)); 95 | withdraw_client(client_chan); 96 | } 97 | -------------------------------------------------------------------------------- /examples/connect.rs: -------------------------------------------------------------------------------- 1 | extern crate session_types; 2 | use session_types::*; 3 | 4 | fn server(c: Chan<(), Eps>) { 5 | c.close() 6 | } 7 | 8 | fn client(c: Chan<(), Eps>) { 9 | c.close() 10 | } 11 | 12 | fn main() { 13 | connect(server, client); 14 | } 15 | -------------------------------------------------------------------------------- /examples/echo-server.rs: -------------------------------------------------------------------------------- 1 | /// This is an implementation of an echo server. 2 | 3 | /// One process reads input and sends it to the other process, which outputs it. 4 | extern crate session_types; 5 | use session_types::*; 6 | 7 | use std::thread::spawn; 8 | 9 | type Srv = Offer>>; 10 | fn srv(c: Chan<(), Rec>) { 11 | let mut c = c.enter(); 12 | 13 | loop { 14 | c = offer! { c, 15 | CLOSE => { 16 | println!("Closing server."); 17 | c.close(); 18 | break 19 | }, 20 | RECV => { 21 | let (c, s) = c.recv(); 22 | println!("Received: {}", s); 23 | c.zero() 24 | } 25 | }; 26 | } 27 | } 28 | 29 | type Cli = ::Dual; 30 | fn cli(c: Chan<(), Rec>) { 31 | let stdin = std::io::stdin(); 32 | let mut count = 0usize; 33 | 34 | let mut c = c.enter(); 35 | let mut buf = String::with_capacity(1024); 36 | loop { 37 | stdin.read_line(&mut buf).ok().unwrap(); 38 | if !buf.is_empty() { 39 | buf.pop(); 40 | } 41 | match &buf[..] { 42 | "q" => { 43 | let c = c.sel2().send(format!("{} lines sent", count)); 44 | c.zero().sel1().close(); 45 | println!("Client quitting"); 46 | break; 47 | } 48 | _ => { 49 | c = c.sel2().send(buf.clone()).zero(); 50 | buf.clear(); 51 | count += 1; 52 | } 53 | } 54 | } 55 | } 56 | 57 | fn main() { 58 | let (c1, c2) = session_channel(); 59 | println!("Starting echo server. Press 'q' to quit."); 60 | let t = spawn(move || srv(c1)); 61 | cli(c2); 62 | let _ = t.join(); 63 | } 64 | -------------------------------------------------------------------------------- /examples/generic.rs: -------------------------------------------------------------------------------- 1 | /// generic.rs 2 | /// 3 | /// This example demonstrates how we can use traits to send values through a 4 | /// channel without actually knowing the type of the value. 5 | extern crate session_types; 6 | use session_types::*; 7 | 8 | use std::thread::spawn; 9 | 10 | fn srv(x: A, c: Chan<(), Send>) { 11 | c.send(x).close(); 12 | } 13 | 14 | fn cli(c: Chan<(), Recv>) { 15 | let (c, x) = c.recv(); 16 | println!("{:?}", x); 17 | c.close(); 18 | } 19 | 20 | fn main() { 21 | let (c1, c2) = session_channel(); 22 | let t = spawn(move || srv(42u8, c1)); 23 | cli(c2); 24 | 25 | t.join().unwrap(); 26 | } 27 | -------------------------------------------------------------------------------- /examples/hselect.rs: -------------------------------------------------------------------------------- 1 | extern crate session_types; 2 | 3 | use session_types::*; 4 | 5 | fn main() { 6 | let (tcs, rcs) = session_channel(); 7 | let (tcu, rcu) = session_channel(); 8 | 9 | let receivers = vec![rcs, rcu]; 10 | 11 | tcs.send("Hello, World from TCS!".to_string()).close(); 12 | 13 | let (ready, mut rest) = hselect(receivers); 14 | 15 | let (to_close, s) = ready.recv(); 16 | println!("Got a response: \"{}\"", s); 17 | to_close.close(); 18 | 19 | tcu.send("Hello, World from TCU!".to_string()).close(); 20 | 21 | rest.drain(..).for_each(|r| { 22 | let (to_close, s) = r.recv(); 23 | println!("Also got this: \"{}\"", s); 24 | to_close.close() 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /examples/many-clients.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] 2 | extern crate rand; 3 | extern crate session_types; 4 | 5 | use rand::random; 6 | use session_types::*; 7 | use std::thread::spawn; 8 | 9 | type Server = Recv, Eps>>; 10 | type Client = ::Dual; 11 | 12 | fn server_handler(c: Chan<(), Server>) { 13 | let (c, n) = c.recv(); 14 | match n.checked_add(42) { 15 | Some(n) => c.sel1().send(n).close(), 16 | None => c.sel2().close(), 17 | } 18 | } 19 | 20 | /// A channel on which we will receive channels 21 | /// 22 | type ChanChan = Offer, Var>>; 23 | 24 | /// server sits in a loop accepting session-typed channels. For each received channel, a new thread 25 | /// is spawned to handle it. 26 | /// 27 | /// When the server is asked to quit, it returns how many connections were handled 28 | fn server(rx: Chan<(), Rec>) -> usize { 29 | let mut count = 0; 30 | let mut c = rx.enter(); 31 | loop { 32 | c = offer! { c, 33 | Quit => { 34 | c.close(); 35 | break 36 | }, 37 | NewChan => { 38 | let (c, new_chan) = c.recv(); 39 | spawn(move || server_handler(new_chan)); 40 | count += 1; 41 | c.zero() 42 | } 43 | } 44 | } 45 | count 46 | } 47 | 48 | fn client_handler(c: Chan<(), Client>) { 49 | let n = random(); 50 | match c.send(n).offer() { 51 | Left(c) => { 52 | let (c, n2) = c.recv(); 53 | c.close(); 54 | println!("{} + 42 = {}", n, n2); 55 | } 56 | Right(c) => { 57 | c.close(); 58 | println!("{} + 42 is an overflow :(", n); 59 | } 60 | } 61 | } 62 | 63 | fn main() { 64 | let (tx, rx) = session_channel(); 65 | 66 | let n: u8 = random(); 67 | let mut tx = tx.enter(); 68 | 69 | println!("Spawning {} clients", n); 70 | let mut ts = vec![]; 71 | for _ in 0..n { 72 | let (c1, c2) = session_channel(); 73 | ts.push(spawn(move || { 74 | client_handler(c2); 75 | })); 76 | tx = tx.sel2().send(c1).zero(); 77 | } 78 | tx.sel1().close(); 79 | let count = server(rx); 80 | for t in ts { 81 | let _ = t.join(); 82 | } 83 | println!("Handled {} connections", count); 84 | } 85 | -------------------------------------------------------------------------------- /examples/planeclip.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "cargo-clippy", allow(many_single_char_names, ptr_arg))] 2 | // This is an implementation of the Sutherland-Hodgman (1974) reentrant polygon 3 | // clipping algorithm. It takes a polygon represented as a number of vertices 4 | // and cuts it according to the given planes. 5 | 6 | // The implementation borrows heavily from Pucella-Tov (2008). See that paper 7 | // for more explanation. 8 | 9 | extern crate rand; 10 | extern crate session_types; 11 | 12 | use session_types::*; 13 | 14 | use rand::distributions::{Distribution, Standard}; 15 | use rand::{random, Rng}; 16 | 17 | use std::thread::spawn; 18 | 19 | #[derive(Debug, Copy, Clone)] 20 | struct Point(f64, f64, f64); 21 | 22 | impl Distribution for Standard { 23 | fn sample(&self, rng: &mut R) -> Point { 24 | Point(rng.gen(), rng.gen(), rng.gen()) 25 | } 26 | } 27 | 28 | #[derive(Debug, Copy, Clone)] 29 | struct Plane(f64, f64, f64, f64); 30 | 31 | impl Distribution for Standard { 32 | fn sample(&self, rng: &mut R) -> Plane { 33 | Plane(rng.gen(), rng.gen(), rng.gen(), rng.gen()) 34 | } 35 | } 36 | 37 | fn above(Point(x, y, z): Point, Plane(a, b, c, d): Plane) -> bool { 38 | (a * x + b * y + c * z + d) / (a * a + b * b + c * c).sqrt() > 0.0 39 | } 40 | 41 | fn intersect(p1: Point, p2: Point, plane: Plane) -> Option { 42 | let Point(x1, y1, z1) = p1; 43 | let Point(x2, y2, z2) = p2; 44 | let Plane(a, b, c, d) = plane; 45 | 46 | if above(p1, plane) == above(p2, plane) { 47 | None 48 | } else { 49 | let t = (a * x1 + b * y1 + c * z1 + d) / (a * (x1 - x2) + b * (y1 - y2) + c * (z1 - z2)); 50 | let x = x1 + (x2 - x1) * t; 51 | let y = y1 + (y2 - y1) * t; 52 | let z = z1 + (z2 - z1) * t; 53 | Some(Point(x, y, z)) 54 | } 55 | } 56 | 57 | type SendList = Rec>>>; 58 | type RecvList = Rec>>>; 59 | 60 | fn sendlist(c: Chan<(), SendList>, xs: &Vec) { 61 | let mut c = c.enter(); 62 | for x in xs { 63 | let c1 = c.sel2().send(*x); 64 | c = c1.zero(); 65 | } 66 | c.sel1().close(); 67 | } 68 | 69 | fn recvlist(c: Chan<(), RecvList>) -> Vec { 70 | let mut v = Vec::new(); 71 | let mut c = c.enter(); 72 | loop { 73 | c = match c.offer() { 74 | Left(c) => { 75 | c.close(); 76 | break; 77 | } 78 | Right(c) => { 79 | let (c, x) = c.recv(); 80 | v.push(x); 81 | c.zero() 82 | } 83 | } 84 | } 85 | 86 | v 87 | } 88 | 89 | fn clipper(plane: Plane, ic: Chan<(), RecvList>, oc: Chan<(), SendList>) { 90 | let mut oc = oc.enter(); 91 | let mut ic = ic.enter(); 92 | let (pt0, mut pt); 93 | 94 | match ic.offer() { 95 | Left(c) => { 96 | c.close(); 97 | oc.sel1().close(); 98 | return; 99 | } 100 | Right(c) => { 101 | let (c, ptz) = c.recv(); 102 | ic = c.zero(); 103 | pt0 = ptz; 104 | pt = ptz; 105 | } 106 | } 107 | 108 | loop { 109 | if above(pt, plane) { 110 | oc = oc.sel2().send(pt).zero(); 111 | } 112 | ic = match ic.offer() { 113 | Left(c) => { 114 | if let Some(pt) = intersect(pt, pt0, plane) { 115 | oc = oc.sel2().send(pt).zero(); 116 | } 117 | c.close(); 118 | oc.sel1().close(); 119 | break; 120 | } 121 | Right(ic) => { 122 | let (ic, pt2) = ic.recv(); 123 | if let Some(pt) = intersect(pt, pt2, plane) { 124 | oc = oc.sel2().send(pt).zero(); 125 | } 126 | pt = pt2; 127 | ic.zero() 128 | } 129 | } 130 | } 131 | } 132 | 133 | fn clipmany(planes: Vec, points: Vec) -> Vec { 134 | let (tx, rx) = session_channel(); 135 | spawn(move || sendlist(tx, &points)); 136 | let mut rx = rx; 137 | 138 | for plane in planes { 139 | let (tx2, rx2) = session_channel(); 140 | spawn(move || clipper(plane, rx, tx2)); 141 | rx = rx2; 142 | } 143 | recvlist(rx) 144 | } 145 | 146 | fn normalize_point(Point(a, b, c): Point) -> Point { 147 | Point(10.0 * (a - 0.5), 10.0 * (b - 0.5), 10.0 * (c - 0.5)) 148 | } 149 | 150 | fn normalize_plane(Plane(a, b, c, d): Plane) -> Plane { 151 | Plane( 152 | 10.0 * (a - 0.5), 153 | 10.0 * (b - 0.5), 154 | 10.0 * (c - 0.5), 155 | 10.0 * (d - 0.5), 156 | ) 157 | } 158 | 159 | fn bench(n: usize, m: usize) { 160 | let points = (0..n).map(|_| random()).map(normalize_point).collect(); 161 | let planes = (0..m).map(|_| random()).map(normalize_plane).collect(); 162 | 163 | let points = clipmany(planes, points); 164 | println!("{}", points.len()); 165 | } 166 | 167 | fn main() { 168 | bench(100, 5); 169 | } 170 | -------------------------------------------------------------------------------- /examples/try-offer.rs: -------------------------------------------------------------------------------- 1 | //! A variation of the echo server where the server wakes up every 20ms 2 | //! and polls for pending messages (single characters in this case): here 3 | //! the "server" echos the received character after converting to uppercase, 4 | //! and the "client" generates a some characters at different intervals. 5 | 6 | extern crate session_types; 7 | use session_types::*; 8 | 9 | type Term = Eps; 10 | 11 | type Upcase = Offer>, Term>; 12 | 13 | type Chargen = ::Dual; 14 | 15 | fn upcase(chan: Chan<(), Rec>) { 16 | let mut chan = chan.enter(); 17 | 'outer: loop { 18 | println!("upcase: tick!"); 19 | let mut poll = true; 20 | while poll { 21 | let result = try_offer! { chan, 22 | ACHAR => { 23 | let (chan, ch) = chan.recv(); 24 | println!("{:?}", ch.to_uppercase().next().unwrap()); 25 | Ok (chan.zero()) 26 | }, 27 | QUIT => { 28 | chan.close(); 29 | break 'outer 30 | } 31 | }; 32 | chan = match result { 33 | Ok(chan) => chan, 34 | Err(chan) => { 35 | poll = false; 36 | chan 37 | } 38 | } 39 | } 40 | std::thread::sleep(std::time::Duration::from_millis(20)); 41 | } 42 | } 43 | 44 | fn chargen(chan: Chan<(), Rec>) { 45 | let mut chan = chan.enter(); 46 | chan = chan.sel1().send('a').zero(); 47 | chan = chan.sel1().send('b').zero(); 48 | chan = chan.sel1().send('c').zero(); 49 | std::thread::sleep(std::time::Duration::from_millis(100)); 50 | chan = chan.sel1().send('d').zero(); 51 | std::thread::sleep(std::time::Duration::from_millis(100)); 52 | chan = chan.sel1().send('e').zero(); 53 | chan.sel2().close(); 54 | } 55 | 56 | fn main() { 57 | let (chan1, chan2) = session_channel(); 58 | let join_handle = std::thread::spawn(move || upcase(chan1)); 59 | chargen(chan2); 60 | join_handle.join().unwrap(); 61 | } 62 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `session_types` 2 | //! 3 | //! This is an implementation of *session types* in Rust. 4 | //! 5 | //! The channels in Rusts standard library are useful for a great many things, 6 | //! but they're restricted to a single type. Session types allows one to use a 7 | //! single channel for transferring values of different types, depending on the 8 | //! context in which it is used. Specifically, a session typed channel always 9 | //! carry a *protocol*, which dictates how communication is to take place. 10 | //! 11 | //! For example, imagine that two threads, `A` and `B` want to communicate with 12 | //! the following pattern: 13 | //! 14 | //! 1. `A` sends an integer to `B`. 15 | //! 2. `B` sends a boolean to `A` depending on the integer received. 16 | //! 17 | //! With session types, this could be done by sharing a single channel. From 18 | //! `A`'s point of view, it would have the type `int ! (bool ? eps)` where `t ! r` 19 | //! is the protocol "send something of type `t` then proceed with 20 | //! protocol `r`", the protocol `t ? r` is "receive something of type `t` then proceed 21 | //! with protocol `r`, and `eps` is a special marker indicating the end of a 22 | //! communication session. 23 | //! 24 | //! Our session type library allows the user to create channels that adhere to a 25 | //! specified protocol. For example, a channel like the above would have the type 26 | //! `Chan<(), Send>>`, and the full program could look like this: 27 | //! 28 | //! ``` 29 | //! extern crate session_types; 30 | //! use session_types::*; 31 | //! 32 | //! type Server = Recv>; 33 | //! type Client = Send>; 34 | //! 35 | //! fn srv(c: Chan<(), Server>) { 36 | //! let (c, n) = c.recv(); 37 | //! if n % 2 == 0 { 38 | //! c.send(true).close() 39 | //! } else { 40 | //! c.send(false).close() 41 | //! } 42 | //! } 43 | //! 44 | //! fn cli(c: Chan<(), Client>) { 45 | //! let n = 42; 46 | //! let c = c.send(n); 47 | //! let (c, b) = c.recv(); 48 | //! 49 | //! if b { 50 | //! println!("{} is even", n); 51 | //! } else { 52 | //! println!("{} is odd", n); 53 | //! } 54 | //! 55 | //! c.close(); 56 | //! } 57 | //! 58 | //! fn main() { 59 | //! connect(srv, cli); 60 | //! } 61 | //! ``` 62 | #![cfg_attr(feature = "cargo-clippy", allow(clippy::double_must_use))] 63 | #![cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))] 64 | extern crate crossbeam_channel; 65 | 66 | use std::marker::PhantomData; 67 | use std::thread::spawn; 68 | use std::{marker, mem, ptr}; 69 | 70 | use std::collections::HashMap; 71 | 72 | use crossbeam_channel::{unbounded, Receiver, Sender}; 73 | 74 | use crossbeam_channel::Select; 75 | 76 | pub use Branch::*; 77 | 78 | /// A session typed channel. `P` is the protocol and `E` is the environment, 79 | /// containing potential recursion targets 80 | #[must_use] 81 | pub struct Chan(Sender<*mut u8>, Receiver<*mut u8>, PhantomData<(E, P)>); 82 | 83 | unsafe impl marker::Send for Chan {} 84 | 85 | unsafe fn write_chan(Chan(tx, _, _): &Chan, x: A) { 86 | tx.send(Box::into_raw(Box::new(x)) as *mut _).unwrap() 87 | } 88 | 89 | unsafe fn read_chan(Chan(_, rx, _): &Chan) -> A { 90 | *Box::from_raw(rx.recv().unwrap() as *mut A) 91 | } 92 | 93 | unsafe fn try_read_chan(Chan(_, rx, _): &Chan) -> Option { 94 | match rx.try_recv() { 95 | Ok(a) => Some(*Box::from_raw(a as *mut A)), 96 | Err(_) => None, 97 | } 98 | } 99 | 100 | /// Peano numbers: Zero 101 | #[allow(missing_copy_implementations)] 102 | pub struct Z; 103 | 104 | /// Peano numbers: Increment 105 | pub struct S(PhantomData); 106 | 107 | /// End of communication session (epsilon) 108 | #[allow(missing_copy_implementations)] 109 | pub struct Eps; 110 | 111 | /// Receive `A`, then `P` 112 | pub struct Recv(PhantomData<(A, P)>); 113 | 114 | /// Send `A`, then `P` 115 | pub struct Send(PhantomData<(A, P)>); 116 | 117 | /// Active choice between `P` and `Q` 118 | pub struct Choose(PhantomData<(P, Q)>); 119 | 120 | /// Passive choice (offer) between `P` and `Q` 121 | pub struct Offer(PhantomData<(P, Q)>); 122 | 123 | /// Enter a recursive environment 124 | pub struct Rec

(PhantomData

); 125 | 126 | /// Recurse. N indicates how many layers of the recursive environment we recurse 127 | /// out of. 128 | pub struct Var(PhantomData); 129 | 130 | /// The HasDual trait defines the dual relationship between protocols. 131 | /// 132 | /// Any valid protocol has a corresponding dual. 133 | /// 134 | /// This trait is sealed and cannot be implemented outside of session-types 135 | pub trait HasDual: private::Sealed { 136 | type Dual; 137 | } 138 | 139 | impl HasDual for Eps { 140 | type Dual = Eps; 141 | } 142 | 143 | impl HasDual for Send { 144 | type Dual = Recv; 145 | } 146 | 147 | impl HasDual for Recv { 148 | type Dual = Send; 149 | } 150 | 151 | impl HasDual for Choose { 152 | type Dual = Offer; 153 | } 154 | 155 | impl HasDual for Offer { 156 | type Dual = Choose; 157 | } 158 | 159 | impl HasDual for Var { 160 | type Dual = Var; 161 | } 162 | 163 | impl HasDual for Var> { 164 | type Dual = Var>; 165 | } 166 | 167 | impl HasDual for Rec

{ 168 | type Dual = Rec; 169 | } 170 | 171 | pub enum Branch { 172 | Left(L), 173 | Right(R), 174 | } 175 | 176 | impl Drop for Chan { 177 | fn drop(&mut self) { 178 | panic!("Session channel prematurely dropped"); 179 | } 180 | } 181 | 182 | impl Chan { 183 | /// Close a channel. Should always be used at the end of your program. 184 | pub fn close(self) { 185 | // This method cleans up the channel without running the panicky destructor 186 | // In essence, it calls the drop glue bypassing the `Drop::drop` method 187 | 188 | let this = mem::ManuallyDrop::new(self); 189 | 190 | let sender = unsafe { ptr::read(&(this).0 as *const _) }; 191 | let receiver = unsafe { ptr::read(&(this).1 as *const _) }; 192 | 193 | drop(sender); 194 | drop(receiver); // drop them 195 | } 196 | } 197 | 198 | impl Chan { 199 | unsafe fn cast(self) -> Chan { 200 | let this = mem::ManuallyDrop::new(self); 201 | Chan( 202 | ptr::read(&(this).0 as *const _), 203 | ptr::read(&(this).1 as *const _), 204 | PhantomData, 205 | ) 206 | } 207 | } 208 | 209 | impl Chan> { 210 | /// Send a value of type `A` over the channel. Returns a channel with 211 | /// protocol `P` 212 | #[must_use] 213 | pub fn send(self, v: A) -> Chan { 214 | unsafe { 215 | write_chan(&self, v); 216 | self.cast() 217 | } 218 | } 219 | } 220 | 221 | impl Chan> { 222 | /// Receives a value of type `A` from the channel. Returns a tuple 223 | /// containing the resulting channel and the received value. 224 | #[must_use] 225 | pub fn recv(self) -> (Chan, A) { 226 | unsafe { 227 | let v = read_chan(&self); 228 | (self.cast(), v) 229 | } 230 | } 231 | 232 | /// Non-blocking receive. 233 | #[must_use] 234 | pub fn try_recv(self) -> Result<(Chan, A), Self> { 235 | unsafe { 236 | if let Some(v) = try_read_chan(&self) { 237 | Ok((self.cast(), v)) 238 | } else { 239 | Err(self) 240 | } 241 | } 242 | } 243 | } 244 | 245 | impl Chan> { 246 | /// Perform an active choice, selecting protocol `P`. 247 | #[must_use] 248 | pub fn sel1(self) -> Chan { 249 | unsafe { 250 | write_chan(&self, true); 251 | self.cast() 252 | } 253 | } 254 | 255 | /// Perform an active choice, selecting protocol `Q`. 256 | #[must_use] 257 | pub fn sel2(self) -> Chan { 258 | unsafe { 259 | write_chan(&self, false); 260 | self.cast() 261 | } 262 | } 263 | } 264 | 265 | /// Convenience function. This is identical to `.sel2()` 266 | impl Chan> { 267 | #[must_use] 268 | pub fn skip(self) -> Chan { 269 | self.sel2() 270 | } 271 | } 272 | 273 | /// Convenience function. This is identical to `.sel2().sel2()` 274 | impl Chan>> { 275 | #[must_use] 276 | pub fn skip2(self) -> Chan { 277 | self.sel2().sel2() 278 | } 279 | } 280 | 281 | /// Convenience function. This is identical to `.sel2().sel2().sel2()` 282 | impl Chan>>> { 283 | #[must_use] 284 | pub fn skip3(self) -> Chan { 285 | self.sel2().sel2().sel2() 286 | } 287 | } 288 | 289 | /// Convenience function. This is identical to `.sel2().sel2().sel2().sel2()` 290 | impl Chan>>>> { 291 | #[must_use] 292 | pub fn skip4(self) -> Chan { 293 | self.sel2().sel2().sel2().sel2() 294 | } 295 | } 296 | 297 | /// Convenience function. This is identical to `.sel2().sel2().sel2().sel2().sel2()` 298 | impl Chan>>>>> { 299 | #[must_use] 300 | pub fn skip5(self) -> Chan { 301 | self.sel2().sel2().sel2().sel2().sel2() 302 | } 303 | } 304 | 305 | /// Convenience function. 306 | impl 307 | Chan>>>>>> 308 | { 309 | #[must_use] 310 | pub fn skip6(self) -> Chan { 311 | self.sel2().sel2().sel2().sel2().sel2().sel2() 312 | } 313 | } 314 | 315 | /// Convenience function. 316 | impl 317 | Chan>>>>>>> 318 | { 319 | #[must_use] 320 | pub fn skip7(self) -> Chan { 321 | self.sel2().sel2().sel2().sel2().sel2().sel2().sel2() 322 | } 323 | } 324 | 325 | impl Chan> { 326 | /// Passive choice. This allows the other end of the channel to select one 327 | /// of two options for continuing the protocol: either `P` or `Q`. 328 | #[must_use] 329 | pub fn offer(self) -> Branch, Chan> { 330 | unsafe { 331 | let b = read_chan(&self); 332 | if b { 333 | Left(self.cast()) 334 | } else { 335 | Right(self.cast()) 336 | } 337 | } 338 | } 339 | 340 | /// Poll for choice. 341 | #[must_use] 342 | pub fn try_offer(self) -> Result, Chan>, Self> { 343 | unsafe { 344 | if let Some(b) = try_read_chan(&self) { 345 | if b { 346 | Ok(Left(self.cast())) 347 | } else { 348 | Ok(Right(self.cast())) 349 | } 350 | } else { 351 | Err(self) 352 | } 353 | } 354 | } 355 | } 356 | 357 | impl Chan> { 358 | /// Enter a recursive environment, putting the current environment on the 359 | /// top of the environment stack. 360 | #[must_use] 361 | pub fn enter(self) -> Chan<(P, E), P> { 362 | unsafe { self.cast() } 363 | } 364 | } 365 | 366 | impl Chan<(P, E), Var> { 367 | /// Recurse to the environment on the top of the environment stack. 368 | #[must_use] 369 | pub fn zero(self) -> Chan<(P, E), P> { 370 | unsafe { self.cast() } 371 | } 372 | } 373 | 374 | impl Chan<(P, E), Var>> { 375 | /// Pop the top environment from the environment stack. 376 | #[must_use] 377 | pub fn succ(self) -> Chan> { 378 | unsafe { self.cast() } 379 | } 380 | } 381 | 382 | /// Homogeneous select. We have a vector of channels, all obeying the same 383 | /// protocol (and in the exact same point of the protocol), wait for one of them 384 | /// to receive. Removes the receiving channel from the vector and returns both 385 | /// the channel and the new vector. 386 | #[must_use] 387 | pub fn hselect( 388 | mut chans: Vec>>, 389 | ) -> (Chan>, Vec>>) { 390 | let i = iselect(&chans); 391 | let c = chans.remove(i); 392 | (c, chans) 393 | } 394 | 395 | /// An alternative version of homogeneous select, returning the index of the Chan 396 | /// that is ready to receive. 397 | pub fn iselect(chans: &[Chan>]) -> usize { 398 | let mut map = HashMap::new(); 399 | 400 | let id = { 401 | let mut sel = Select::new(); 402 | let mut handles = Vec::with_capacity(chans.len()); // collect all the handles 403 | 404 | for (i, chan) in chans.iter().enumerate() { 405 | let Chan(_, rx, _) = chan; 406 | let handle = sel.recv(rx); 407 | map.insert(handle, i); 408 | handles.push(handle); 409 | } 410 | 411 | sel.ready() 412 | }; 413 | map.remove(&id).unwrap() 414 | } 415 | 416 | /// Heterogeneous selection structure for channels 417 | /// 418 | /// This builds a structure of channels that we wish to select over. This is 419 | /// structured in a way such that the channels selected over cannot be 420 | /// interacted with (consumed) as long as the borrowing ChanSelect object 421 | /// exists. This is necessary to ensure memory safety. 422 | /// 423 | /// The type parameter T is a return type, ie we store a value of some type T 424 | /// that is returned in case its associated channels is selected on `wait()` 425 | pub struct ChanSelect<'c> { 426 | receivers: Vec<&'c Receiver<*mut u8>>, 427 | } 428 | 429 | impl<'c> ChanSelect<'c> { 430 | pub fn new() -> ChanSelect<'c> { 431 | ChanSelect { 432 | receivers: Vec::new(), 433 | } 434 | } 435 | 436 | /// Add a channel whose next step is `Recv` 437 | /// 438 | /// Once a channel has been added it cannot be interacted with as long as it 439 | /// is borrowed here (by virtue of borrow checking and lifetimes). 440 | pub fn add_recv(&mut self, chan: &'c Chan>) { 441 | let Chan(_, rx, _) = chan; 442 | self.receivers.push(rx); 443 | } 444 | 445 | pub fn add_offer(&mut self, chan: &'c Chan>) { 446 | let Chan(_, rx, _) = chan; 447 | self.receivers.push(rx); 448 | } 449 | 450 | /// Find a Receiver (and hence a Chan) that is ready to receive. 451 | /// 452 | /// This method consumes the ChanSelect, freeing up the borrowed Receivers 453 | /// to be consumed. 454 | pub fn wait(self) -> usize { 455 | let mut sel = Select::new(); 456 | for rx in self.receivers.into_iter() { 457 | sel.recv(rx); 458 | } 459 | 460 | sel.ready() 461 | } 462 | 463 | /// How many channels are there in the structure? 464 | pub fn len(&self) -> usize { 465 | self.receivers.len() 466 | } 467 | 468 | pub fn is_empty(&self) -> bool { 469 | self.receivers.is_empty() 470 | } 471 | } 472 | 473 | impl<'c> Default for ChanSelect<'c> { 474 | fn default() -> Self { 475 | Self::new() 476 | } 477 | } 478 | 479 | /// Returns two session channels 480 | #[must_use] 481 | pub fn session_channel() -> (Chan<(), P>, Chan<(), P::Dual>) { 482 | let (tx1, rx1) = unbounded(); 483 | let (tx2, rx2) = unbounded(); 484 | 485 | let c1 = Chan(tx1, rx2, PhantomData); 486 | let c2 = Chan(tx2, rx1, PhantomData); 487 | 488 | (c1, c2) 489 | } 490 | 491 | /// Connect two functions using a session typed channel. 492 | pub fn connect(srv: F1, cli: F2) 493 | where 494 | F1: Fn(Chan<(), P>) + marker::Send + 'static, 495 | F2: Fn(Chan<(), P::Dual>) + marker::Send, 496 | P: HasDual + marker::Send + 'static, 497 | P::Dual: HasDual + marker::Send + 'static, 498 | { 499 | let (c1, c2) = session_channel(); 500 | let t = spawn(move || srv(c1)); 501 | cli(c2); 502 | t.join().unwrap(); 503 | } 504 | 505 | mod private { 506 | use super::*; 507 | pub trait Sealed {} 508 | 509 | // Impl for all exported protocol types 510 | impl Sealed for Eps {} 511 | impl Sealed for Send {} 512 | impl Sealed for Recv {} 513 | impl Sealed for Choose {} 514 | impl Sealed for Offer {} 515 | impl Sealed for Var {} 516 | impl

Sealed for Rec

{} 517 | } 518 | 519 | /// This macro is convenient for server-like protocols of the form: 520 | /// 521 | /// `Offer>>` 522 | /// 523 | /// # Examples 524 | /// 525 | /// Assume we have a protocol `Offer, Offer,Eps>>>` 526 | /// we can use the `offer!` macro as follows: 527 | /// 528 | /// ```rust 529 | /// extern crate session_types; 530 | /// use session_types::*; 531 | /// use std::thread::spawn; 532 | /// 533 | /// fn srv(c: Chan<(), Offer, Offer, Eps>>>) { 534 | /// offer! { c, 535 | /// Number => { 536 | /// let (c, n) = c.recv(); 537 | /// assert_eq!(42, n); 538 | /// c.close(); 539 | /// }, 540 | /// String => { 541 | /// c.recv().0.close(); 542 | /// }, 543 | /// Quit => { 544 | /// c.close(); 545 | /// } 546 | /// } 547 | /// } 548 | /// 549 | /// fn cli(c: Chan<(), Choose, Choose, Eps>>>) { 550 | /// c.sel1().send(42).close(); 551 | /// } 552 | /// 553 | /// fn main() { 554 | /// let (s, c) = session_channel(); 555 | /// spawn(move|| cli(c)); 556 | /// srv(s); 557 | /// } 558 | /// ``` 559 | /// 560 | /// The identifiers on the left-hand side of the arrows have no semantic 561 | /// meaning, they only provide a meaningful name for the reader. 562 | #[macro_export] 563 | macro_rules! offer { 564 | ( 565 | $id:ident, $branch:ident => $code:expr, $($t:tt)+ 566 | ) => ( 567 | match $id.offer() { 568 | $crate::Left($id) => $code, 569 | $crate::Right($id) => offer!{ $id, $($t)+ } 570 | } 571 | ); 572 | ( 573 | $id:ident, $branch:ident => $code:expr 574 | ) => ( 575 | $code 576 | ) 577 | } 578 | 579 | /// Returns the channel unchanged on `TryRecvError::Empty`. 580 | #[macro_export] 581 | macro_rules! try_offer { 582 | ( 583 | $id:ident, $branch:ident => $code:expr, $($t:tt)+ 584 | ) => ( 585 | match $id.try_offer() { 586 | Ok($crate::Left($id)) => $code, 587 | Ok($crate::Right($id)) => try_offer!{ $id, $($t)+ }, 588 | Err($id) => Err($id) 589 | } 590 | ); 591 | ( 592 | $id:ident, $branch:ident => $code:expr 593 | ) => ( 594 | $code 595 | ) 596 | } 597 | 598 | /// This macro plays the same role as the `select!` macro does for `Receiver`s. 599 | /// 600 | /// It also supports a second form with `Offer`s (see the example below). 601 | /// 602 | /// # Examples 603 | /// 604 | /// ```rust 605 | /// extern crate session_types; 606 | /// use session_types::*; 607 | /// use std::thread::spawn; 608 | /// 609 | /// fn send_str(c: Chan<(), Send>) { 610 | /// c.send("Hello, World!".to_string()).close(); 611 | /// } 612 | /// 613 | /// fn send_usize(c: Chan<(), Send>) { 614 | /// c.send(42).close(); 615 | /// } 616 | /// 617 | /// fn main() { 618 | /// let (tcs, rcs) = session_channel(); 619 | /// let (tcu, rcu) = session_channel(); 620 | /// 621 | /// // Spawn threads 622 | /// spawn(move|| send_str(tcs)); 623 | /// spawn(move|| send_usize(tcu)); 624 | /// 625 | /// chan_select! { 626 | /// (c, s) = rcs.recv() => { 627 | /// assert_eq!("Hello, World!".to_string(), s); 628 | /// c.close(); 629 | /// rcu.recv().0.close(); 630 | /// }, 631 | /// (c, i) = rcu.recv() => { 632 | /// assert_eq!(42, i); 633 | /// c.close(); 634 | /// rcs.recv().0.close(); 635 | /// } 636 | /// } 637 | /// } 638 | /// ``` 639 | /// 640 | /// ```rust 641 | /// extern crate session_types; 642 | /// extern crate rand; 643 | /// 644 | /// use std::thread::spawn; 645 | /// use session_types::*; 646 | /// 647 | /// type Igo = Choose, Send>; 648 | /// type Ugo = Offer, Recv>; 649 | /// 650 | /// fn srv(chan_one: Chan<(), Ugo>, chan_two: Chan<(), Ugo>) { 651 | /// let _ign; 652 | /// chan_select! { 653 | /// _ign = chan_one.offer() => { 654 | /// String => { 655 | /// let (c, s) = chan_one.recv(); 656 | /// assert_eq!("Hello, World!".to_string(), s); 657 | /// c.close(); 658 | /// match chan_two.offer() { 659 | /// Left(c) => c.recv().0.close(), 660 | /// Right(c) => c.recv().0.close(), 661 | /// } 662 | /// }, 663 | /// Number => { 664 | /// chan_one.recv().0.close(); 665 | /// match chan_two.offer() { 666 | /// Left(c) => c.recv().0.close(), 667 | /// Right(c) => c.recv().0.close(), 668 | /// } 669 | /// } 670 | /// }, 671 | /// _ign = chan_two.offer() => { 672 | /// String => { 673 | /// chan_two.recv().0.close(); 674 | /// match chan_one.offer() { 675 | /// Left(c) => c.recv().0.close(), 676 | /// Right(c) => c.recv().0.close(), 677 | /// } 678 | /// }, 679 | /// Number => { 680 | /// chan_two.recv().0.close(); 681 | /// match chan_one.offer() { 682 | /// Left(c) => c.recv().0.close(), 683 | /// Right(c) => c.recv().0.close(), 684 | /// } 685 | /// } 686 | /// } 687 | /// } 688 | /// } 689 | /// 690 | /// fn cli(c: Chan<(), Igo>) { 691 | /// c.sel1().send("Hello, World!".to_string()).close(); 692 | /// } 693 | /// 694 | /// fn main() { 695 | /// let (ca1, ca2) = session_channel(); 696 | /// let (cb1, cb2) = session_channel(); 697 | /// 698 | /// cb2.sel2().send(42).close(); 699 | /// 700 | /// spawn(move|| cli(ca2)); 701 | /// 702 | /// srv(ca1, cb1); 703 | /// } 704 | /// ``` 705 | #[macro_export] 706 | macro_rules! chan_select { 707 | ( 708 | $(($c:ident, $name:pat) = $rx:ident.recv() => $code:expr),+ 709 | ) => ({ 710 | let index = { 711 | let mut sel = $crate::ChanSelect::new(); 712 | $( sel.add_recv(&$rx); )+ 713 | sel.wait() 714 | }; 715 | let mut i = 0; 716 | $( if index == { i += 1; i - 1 } { let ($c, $name) = $rx.recv(); $code } 717 | else )+ 718 | { unreachable!() } 719 | }); 720 | 721 | ( 722 | $($res:ident = $rx:ident.offer() => { $($t:tt)+ }),+ 723 | ) => ({ 724 | let index = { 725 | let mut sel = $crate::ChanSelect::new(); 726 | $( sel.add_offer(&$rx); )+ 727 | sel.wait() 728 | }; 729 | let mut i = 0; 730 | $( if index == { i += 1; i - 1 } { $res = offer!{ $rx, $($t)+ } } else )+ 731 | { unreachable!() } 732 | }) 733 | } 734 | -------------------------------------------------------------------------------- /tests/chan_select.rs: -------------------------------------------------------------------------------- 1 | extern crate session_types; 2 | 3 | use session_types::*; 4 | 5 | // recv and assert a value, then close the channel 6 | macro_rules! recv_assert_eq_close( 7 | ($e:expr, $rx:ident.recv()) 8 | => 9 | ({ 10 | let (c, v) = $rx.recv(); 11 | assert_eq!($e, v); 12 | c.close(); 13 | }) 14 | ); 15 | 16 | #[test] 17 | fn chan_select_hselect() { 18 | let (tcs, rcs) = session_channel(); 19 | let (tcu, rcu) = session_channel(); 20 | 21 | let receivers = vec![rcs, rcu]; 22 | 23 | tcs.send(1u64).close(); 24 | 25 | let (ready, mut rest) = hselect(receivers); 26 | 27 | let (to_close, received) = ready.recv(); 28 | assert_eq!(received, 1u64); 29 | to_close.close(); 30 | 31 | tcu.send(2u64).close(); 32 | 33 | rest.drain(..).for_each(|r| { 34 | let (to_close, received) = r.recv(); 35 | assert_eq!(received, 2u64); 36 | to_close.close() 37 | }); 38 | } 39 | 40 | #[test] 41 | fn chan_select_simple() { 42 | let (tcs, rcs) = session_channel(); 43 | let (tcu, rcu) = session_channel(); 44 | 45 | // Spawn threads 46 | send_str(tcs); 47 | 48 | // The lifetime of `sel` is reduced to the point where we call 49 | // `wait()`. This ensures we don't hold on to Chan references, but still 50 | // prevents using the channels the ChanSelect holds references to. 51 | let index = { 52 | let mut sel = ChanSelect::new(); 53 | sel.add_recv(&rcs); // Assigned 0 54 | sel.add_recv(&rcu); // Assigned 1 55 | sel.wait() // Destroys the ChanSelect, releases references to 56 | // rcs and rcu 57 | }; 58 | 59 | assert_eq!(0, index); 60 | recv_assert_eq_close!("Hello, World!".to_owned(), rcs.recv()); 61 | 62 | let (tcs, rcs) = session_channel(); 63 | 64 | send_usize(tcu); 65 | 66 | let index = { 67 | let mut sel = ChanSelect::new(); 68 | sel.add_recv(&rcs); 69 | sel.add_recv(&rcu); 70 | sel.wait() 71 | }; 72 | 73 | assert_eq!(1, index); 74 | recv_assert_eq_close!(42, rcu.recv()); 75 | 76 | // Not really necessary for the test, just used to coerce the types of 77 | // tcs and rcs 78 | send_str(tcs); 79 | recv_assert_eq_close!("Hello, World!".to_owned(), rcs.recv()); 80 | } 81 | 82 | // Utility functions 83 | 84 | fn send_str(c: Chan<(), Send>) { 85 | c.send("Hello, World!".to_string()).close(); 86 | } 87 | 88 | fn send_usize(c: Chan<(), Send>) { 89 | c.send(42).close(); 90 | } 91 | -------------------------------------------------------------------------------- /tests/compile-fail/cannot-impl-hasdual.rs: -------------------------------------------------------------------------------- 1 | extern crate session_types; 2 | 3 | use session_types::HasDual; 4 | 5 | struct CustomProto; 6 | 7 | impl HasDual for CustomProto { //~ ERROR the trait bound `CustomProto: session_types::private::Sealed` is not satisfied 8 | type Dual = CustomProto; 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/compile-fail/incompatible-sessions.rs: -------------------------------------------------------------------------------- 1 | extern crate session_types; 2 | 3 | use std::thread::spawn; 4 | 5 | use session_types::*; 6 | 7 | fn srv(c: Chan<(), Recv>) { 8 | let (c, x) = c.recv(); 9 | c.close(); 10 | } 11 | 12 | fn main() { 13 | let (c1, c2) = session_channel(); 14 | let t1 = spawn(|| { srv(c1) }); 15 | 16 | srv(c2); //~ ERROR 17 | c2.close(); //~ ERROR 18 | c2.sel1(); //~ ERROR 19 | c2.sel2(); //~ ERROR 20 | c2.offer(); //~ ERROR 21 | c2.enter(); //~ ERROR 22 | c2.zero(); //~ ERROR 23 | c2.succ(); //~ ERROR 24 | c2.recv(); //~ ERROR 25 | 26 | c2.send(42).close(); 27 | 28 | t1.join().unwrap(); 29 | } 30 | -------------------------------------------------------------------------------- /tests/compile-fail/no-aliasing.rs: -------------------------------------------------------------------------------- 1 | extern crate session_types; 2 | 3 | use std::thread::spawn; 4 | 5 | use session_types::*; 6 | 7 | fn srv(c: Chan<(), Recv>) { 8 | let (c, _) = c.recv(); 9 | c.close(); 10 | } 11 | 12 | fn main() { 13 | let (c1, c2) = session_channel(); 14 | let t1 = spawn(|| { srv(c2) }); 15 | 16 | let c1_ = c1; 17 | c1_.send(42).close(); 18 | c1.send(42).close(); //~ ERROR 19 | 20 | t1.join().unwrap(); 21 | } 22 | -------------------------------------------------------------------------------- /tests/compile-fail/send-on-recv.rs: -------------------------------------------------------------------------------- 1 | extern crate session_types; 2 | 3 | use std::thread::spawn; 4 | 5 | use session_types::*; 6 | 7 | type Proto = Send; 8 | 9 | fn srv(c: Chan<(), Proto>) { 10 | c.send(42).close(); 11 | } 12 | 13 | fn cli(c: Chan<(), ::Dual>) { 14 | c.send(42).close(); //~ ERROR 15 | } 16 | 17 | fn main() { 18 | let (c1, c2) = session_channel(); 19 | let t1 = spawn(|| { srv(c1) }); 20 | cli(c2); 21 | t1.join().unwrap(); 22 | } 23 | -------------------------------------------------------------------------------- /tests/drop_server.rs: -------------------------------------------------------------------------------- 1 | extern crate session_types; 2 | 3 | use session_types::*; 4 | 5 | fn client(c: Chan<(), Send<(), Eps>>) { 6 | c.send(()).close(); 7 | } 8 | 9 | fn server(c: Chan<(), Recv<(), Eps>>) { 10 | let (c, ()) = c.recv(); 11 | c.close(); 12 | } 13 | 14 | fn drop_client(_c: Chan<(), Send<(), Eps>>) {} 15 | 16 | fn drop_server(_c: Chan<(), Recv<(), Eps>>) {} 17 | 18 | #[test] 19 | fn server_client_works() { 20 | connect(server, client); 21 | } 22 | 23 | #[test] 24 | #[should_panic] 25 | fn client_incomplete_panics() { 26 | connect(server, drop_client); 27 | } 28 | 29 | #[test] 30 | #[should_panic] 31 | fn server_incomplete_panics() { 32 | connect(drop_server, client); 33 | } 34 | 35 | #[test] 36 | #[should_panic] 37 | fn server_client_incomplete_panics() { 38 | connect(drop_server, drop_client); 39 | } 40 | -------------------------------------------------------------------------------- /tests/send_recv.rs: -------------------------------------------------------------------------------- 1 | extern crate session_types; 2 | use session_types::*; 3 | 4 | use std::thread::spawn; 5 | 6 | fn client(n: u64, c: Chan<(), Send>) { 7 | c.send(n).close() 8 | } 9 | 10 | #[test] 11 | fn main() { 12 | let n = 42; 13 | let (c1, c2) = session_channel(); 14 | spawn(move || client(n, c1)); 15 | 16 | let (c, n_) = c2.recv(); 17 | c.close(); 18 | assert_eq!(n, n_); 19 | } 20 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | extern crate compiletest_rs as compiletest; 2 | 3 | use std::path::PathBuf; 4 | 5 | fn run_mode(mode: &'static str) { 6 | let mut config = compiletest::Config::default(); 7 | 8 | config.mode = mode.parse().expect("Invalid mode"); 9 | config.src_base = PathBuf::from(format!("tests/{}", mode)); 10 | config.target_rustcflags = Some("-L target/debug -L target/debug/deps".to_string()); 11 | 12 | compiletest::run_tests(&config); 13 | } 14 | 15 | #[test] 16 | fn compile_test() { 17 | run_mode("compile-fail"); 18 | } 19 | -------------------------------------------------------------------------------- /tests/try_recv.rs: -------------------------------------------------------------------------------- 1 | extern crate session_types; 2 | use session_types::*; 3 | 4 | use std::thread::spawn; 5 | 6 | fn client(n: u64, c: Chan<(), Send>) { 7 | c.send(n).close() 8 | } 9 | 10 | #[test] 11 | fn main() { 12 | let n = 42; 13 | let (c1, c2) = session_channel(); 14 | 15 | let res = c2.try_recv(); 16 | assert!(res.is_err()); 17 | let c2 = res.err().unwrap(); 18 | 19 | let client_thread = spawn(move || client(n, c1)); 20 | client_thread.join().unwrap(); 21 | 22 | let res = c2.try_recv(); 23 | assert!(res.is_ok()); 24 | let (c, n_) = res.ok().unwrap(); 25 | assert_eq!(n, n_); 26 | 27 | c.close(); 28 | } 29 | --------------------------------------------------------------------------------