├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── dialectic-compiler ├── Cargo.toml ├── LICENSE ├── README.md ├── src │ ├── cfg.rs │ ├── flow.rs │ ├── lib.rs │ ├── parse.rs │ ├── syntax.rs │ └── target.rs └── tests │ ├── common.rs │ ├── misc_syntax.rs │ ├── parser_roundtrip.rs │ └── tally_client.rs ├── dialectic-macro ├── Cargo.toml ├── LICENSE ├── README.md ├── src │ └── lib.rs └── tests │ ├── loop_break_scope.rs │ ├── loop_choose_empty.rs │ ├── lots_of_jumps.rs │ └── optional_terminators.rs ├── dialectic-null ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── dialectic-reconnect ├── Cargo.toml ├── examples │ ├── ping.rs │ └── pong.rs └── src │ ├── backoff.rs │ ├── lib.rs │ ├── maybe_bounded.rs │ ├── resume.rs │ ├── resume │ └── end.rs │ ├── retry.rs │ ├── retry │ └── end.rs │ └── util.rs ├── dialectic-tokio-mpsc ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── dialectic-tokio-serde-bincode ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── dialectic-tokio-serde-json ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── dialectic-tokio-serde ├── Cargo.toml ├── LICENSE ├── README.md └── src │ ├── error.rs │ └── lib.rs ├── dialectic ├── Cargo.toml ├── LICENSE ├── benches │ ├── micro.rs │ └── mpsc.rs ├── build.rs ├── examples │ ├── README.md │ ├── common.rs │ ├── hello.rs │ ├── stack.rs │ ├── tally.rs │ └── template.rs ├── new-example └── src │ ├── backend.rs │ ├── backend │ └── choice.rs │ ├── chan.rs │ ├── error.rs │ ├── lib.rs │ ├── session.rs │ ├── tuple.rs │ ├── tutorial.rs │ ├── types.rs │ ├── types │ ├── call.rs │ ├── choose.rs │ ├── continue.rs │ ├── done.rs │ ├── loop.rs │ ├── offer.rs │ ├── recv.rs │ ├── send.rs │ └── split.rs │ └── unary.rs ├── readme.py ├── rust-toolchain └── rustfmt.toml /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | 19 | - uses: actions/cache@v2 20 | with: 21 | path: | 22 | ~/.cargo/registry 23 | ~/.cargo/git 24 | target 25 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 26 | 27 | - uses: actions/checkout@v2 28 | 29 | - uses: actions-rs/toolchain@v1 30 | with: 31 | components: rustfmt, clippy 32 | 33 | - name: Format check 34 | run: cargo fmt -- --check 35 | - name: Build 36 | run: cargo build --all-features --all-targets --verbose 37 | - name: Clippy lint 38 | run: cargo clippy --all-features --all-targets --verbose -- -Dwarnings 39 | - name: Run all tests except doctests 40 | run: cargo test --all-features --all-targets --verbose 41 | - name: Run all doctests 42 | run: cargo test --all-features --doc --verbose 43 | - name: Build documentation 44 | run: cargo doc --all-features --no-deps --verbose 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Dialectic 2 | 3 | Glad to see you reading this! We really appreciate the help. For simple bug fixes, just submit a PR 4 | with the fix and we can discuss the fix directly in the PR. If the fix is more complex, start with an 5 | issue. 6 | 7 | ## Getting started 8 | 9 | We keep the internals of Dialectic very well-documented. Here are a few places to start: 10 | 11 | - The [Dialectic docs themselves](https://docs.rs/dialectic) contain documentation which covers some 12 | of the internals. 13 | - The session macro compiler implementation (`dialectic-compiler`) has its own 14 | [readme](https://github.com/boltlabs-inc/dialectic/tree/main/dialectic-compiler/README.md) 15 | explaining the compilation pipeline. 16 | - For resources on how to implement a backend, you can check out the existing 17 | [`dialectic-tokio-mpsc`](https://github.com/boltlabs-inc/dialectic/tree/main/dialectic-tokio-mpsc) 18 | and 19 | [`dialectic-tokio-serde`](https://github.com/boltlabs-inc/dialectic/tree/main/dialectic-tokio-serde) 20 | crates, which are pretty readable and well-documented. 21 | 22 | ## Testing 23 | 24 | Testing differs among the various crates. When it comes to the `dialectic` crate itself, "tests" 25 | consist of the examples (`dialectic/examples`), benchmarks (`dialectic/benches`), and a lot of 26 | doc-tests. Since `dialectic` itself contains mostly compile-time complexity, the vast majority of 27 | necessary tests are basically compile-pass. There are exceptions to this, which mostly use the null 28 | backend (the `micro` benchmark) and the [tokio MPSC 29 | backend](https://github.com/boltlabs-inc/dialectic/tree/main/dialectic-tokio-mpsc), which is the 30 | backend used for doc-tests/examples that actually do something. The [tokio serde/bincode 31 | backend](https://github.com/boltlabs-inc/dialectic/tree/main/dialectic-tokio-serde-bincode) is also 32 | used for the examples in the `dialectic/examples` directory. 33 | 34 | For the `dialectic-macro` crate, proc macros are tested both inside `dialectic-macro` *and* for the 35 | `Session!` macro *specifically* within the `dialectic-compiler` crate. The difference between these 36 | tests are that the `dialectic-macro` tests cover invocations of the `Session!` macro and other proc 37 | macros, ensuring that they compile; and the `dialectic-compiler` tests cover internals and exposed 38 | machinery of the session macro compiler which is used internally in `dialectic-macro`. This allows 39 | things like the `dialectic-compiler` parser to be rigorously tested without having to go through the 40 | Rust compiler. 41 | 42 | ## Coding conventions 43 | 44 | Please use `rustfmt` to format your code. :) 45 | We have an empty `rustfmt.toml` inside the workspace root; this is intentional, so that your 46 | `rustfmt` install will use the default options (our preference.) 47 | 48 | Most dialectic crates contain a number of linting options defined at the crate level, usually 49 | something like this: 50 | 51 | ```rust 52 | #![warn(missing_docs)] 53 | #![warn(missing_copy_implementations, missing_debug_implementations)] 54 | #![warn(unused_qualifications, unused_results)] 55 | #![warn(future_incompatible)] 56 | #![warn(unused)] 57 | // Documentation configuration 58 | #![forbid(broken_intra_doc_links)] 59 | ``` 60 | 61 | Note that warnings are denied during our CI process, so if you are missing documentation or such, 62 | your PR *will* fail CI. We also use `clippy` in our CI and disallow warnings there as well. 63 | 64 | Thank you again for contributing, we really appreciate it! If you have further questions, please 65 | feel free to contact the maintainers: Kenny ([@kwf](https://github.com/kwf) on GitHub / [kwf@boltlabs.io]) or Shea 66 | ([@sdleffler](https://github.com/sdleffler) on GitHub / [shea@errno.com]). 67 | 68 | ## Code of Conduct 69 | 70 | Please be kind, courteous, and respectful. This project, although not formally affiliated with the 71 | Rust project, supports the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct). 72 | Please report any violations of this code of conduct to 73 | [conduct@boltlabs.io](mailto:conduct@boltlabs.io). 74 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "dialectic", 4 | "dialectic-compiler", 5 | "dialectic-macro", 6 | "dialectic-null", 7 | "dialectic-reconnect", 8 | "dialectic-tokio-mpsc", 9 | "dialectic-tokio-serde", 10 | "dialectic-tokio-serde-bincode", 11 | "dialectic-tokio-serde-json", 12 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kenny Foner and Bolt Labs, Inc. 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 | # Dialectic 2 | 3 | [![Rust](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml/badge.svg)](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml) 4 | ![license: MIT](https://img.shields.io/github/license/boltlabs-inc/dialectic) 5 | [![crates.io](https://img.shields.io/crates/v/dialectic)](https://crates.io/crates/dialectic) 6 | [![docs.rs documentation](https://docs.rs/dialectic/badge.svg)](https://docs.rs/dialectic) 7 | 8 | > **dialectic (noun):** The process of arriving at the truth by stating a thesis, developing a 9 | > contradictory antithesis, and combining them into a coherent synthesis. 10 | > 11 | > **dialectic (crate):** Transport-polymorphic session types for asynchronous Rust. 12 | 13 | When two concurrent processes communicate, it's good to give their messages *types*, which 14 | ensure every message is of an expected form. 15 | 16 | - **Conventional types** merely describe **what is valid** to communicate. 17 | - **Session types** describe **when it is valid** to communicate, and **in what manner**. 18 | 19 | This crate provides a generic wrapper around almost any type of asynchronous channel that adds 20 | compile-time guarantees that a specified *session protocol* will not be violated by any code 21 | using the channel. Such a wrapped channel: 22 | 23 | - has **almost no runtime cost** in time or memory; 24 | - is **built on `async`/`.await`** to allow integration with Rust's powerful `async` ecosystem; 25 | - gracefully handles runtime protocol violations, introducing **no panics**; 26 | - allows for **full duplex concurrent communication**, if specified in its type, while 27 | preserving all the same session-type safety guarantees; and 28 | - can even implement **context free sessions**, a more general form of session type than 29 | supported by most other session typing libraries. 30 | 31 | Together, these make Dialectic ideal for writing networked services that need to ensure **high 32 | levels of availability** and **complex protocol correctness properties** in the real world, 33 | where protocols might be violated and connections might be dropped. 34 | 35 | Dialectic supports a number of async runtimes and backends out-of-the-box, if you don't want to 36 | or don't need to write your own: 37 | 38 | - The [`dialectic-tokio-mpsc`] crate supports using Dialectic to communicate between 39 | tasks using Tokio's [`mpsc`] queues. 40 | - The [`dialectic-tokio-serde`] crate supports using Dialectic to communicate over any [`AsyncRead`]/[`AsyncWrite`] transport layer encoded using any Tokio [`codec`]. A couple of Serde formats are already implemented, but it is easy to implement your own: 41 | - [`dialectic-tokio-serde-bincode`] backend using [`bincode`] for serialization 42 | - [`dialectic-tokio-serde-json`] backend using [`serde_json`] for serialization 43 | 44 | These crates also serve as good references for writing your own backends. 45 | 46 | ## What now? 47 | 48 | - If you are **new to session types** you might consider starting with the **[tutorial-style 49 | tour of the crate]**. 50 | - If you're **familiar with session types**, you might jump to the **[quick 51 | reference]**, then read more about the [`Session!`] macro for specifying session types, and 52 | continue on to look at the [`types`] module and the documentation for [`Chan`]. 53 | - You may also find helpful the **[full self-contained 54 | examples](https://github.com/boltlabs-inc/dialectic/tree/main/dialectic/examples)**, which show how all 55 | the features of the crate come together to build session-typed network programs. 56 | - If you want to **integrate your own channel type** with Dialectic, you need to implement the 57 | [`Transmit`] and [`Receive`] traits from the [`backend`] module. 58 | - Or, you can dive into the **[reference documentation]**... 59 | 60 | [`codec`]: https://docs.rs/tokio-util/latest/tokio_util/codec/index.html 61 | [`mpsc`]: https://docs.rs/tokio/latest/tokio/sync/mpsc/index.html 62 | [`AsyncRead`]: https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html 63 | [`AsyncWrite`]: https://docs.rs/tokio/latest/tokio/io/trait.AsyncWrite.html 64 | 65 | [`dialectic-tokio-mpsc`]: https://crates.io/crates/dialectic-tokio-mpsc 66 | [`dialectic-tokio-serde`]: https://crates.io/crates/dialectic-tokio-serde 67 | [`dialectic-tokio-serde-bincode`]: https://crates.io/crates/dialectic-tokio-serde-bincode 68 | [`dialectic-tokio-serde-json`]: https://crates.io/crates/dialectic-tokio-serde-json 69 | [`bincode`]: https://crates.io/crates/bincode 70 | [`serde_json`]: https://crates.io/crates/serde_json 71 | [tutorial-style tour of the crate]: https://docs.rs/dialectic/latest/dialectic/tutorial/index.html 72 | [quick reference]: https://docs.rs/dialectic/latest/dialectic/#quick-reference 73 | [reference documentation]: https://docs.rs/dialectic 74 | [`types`]: https://docs.rs/dialectic/latest/dialectic/types/index.html 75 | [`Chan`]: https://docs.rs/dialectic/latest/dialectic/struct.Chan.html 76 | [`Transmit`]: https://docs.rs/dialectic/latest/dialectic/backend/trait.Transmit.html 77 | [`Receive`]: https://docs.rs/dialectic/latest/dialectic/backend/trait.Receive.html 78 | [`backend`]: https://docs.rs/dialectic/latest/dialectic/backend/index.html 79 | [`Session!`]: https://docs.rs/dialectic/latest/dialectic/macro.Session.html 80 | -------------------------------------------------------------------------------- /dialectic-compiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dialectic-compiler" 3 | version = "0.1.0" 4 | authors = ["Shea Leffler ", "Kenny Foner "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Session type macro compiler for the Dialectic crate" 8 | repository = "https://github.com/boltlabs-inc/dialectic" 9 | homepage = "https://github.com/boltlabs-inc/dialectic" 10 | keywords = ["session", "types", "async", "channel", "macro"] 11 | categories = ["asynchronous", "concurrency"] 12 | 13 | [dependencies] 14 | thunderdome = "0.4" 15 | syn = { version = "1.0", features = ["full", "extra-traits"] } 16 | proc-macro2 = "1.0" 17 | quote = "1.0" 18 | proc-macro-crate = "1.0" 19 | thiserror = "1.0" 20 | quickcheck = { version = "1.0", optional = true } 21 | 22 | [[test]] 23 | name = "parser_roundtrip" 24 | path = "tests/parser_roundtrip.rs" 25 | required-features = ["quickcheck"] 26 | 27 | [package.metadata.docs.rs] 28 | all-features = true 29 | rustdoc-args = ["--cfg", "docsrs"] -------------------------------------------------------------------------------- /dialectic-compiler/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /dialectic-compiler/src/target.rs: -------------------------------------------------------------------------------- 1 | //! The target language of the `Session!` macro, produced by the compiler. 2 | 3 | use { 4 | proc_macro2::TokenStream, 5 | quote::{quote_spanned, ToTokens}, 6 | std::{fmt, rc::Rc}, 7 | syn::{Path, Type}, 8 | }; 9 | 10 | use crate::Spanned; 11 | 12 | /// The target language of the macro: the type level language of session types in Dialectic. 13 | /// 14 | /// This is a one-to-one mapping to the literal syntax you would write without using the `Session!` 15 | /// macro. The only constructors which don't correspond directly to constructs with `Session` 16 | /// implementations are [`Target::Then`], which translates to a type-level function invocation to 17 | /// concatenate two session types, and [`Target::Type`], which translates to an embedding of some 18 | /// arbitrary session type by name (i.e. defined elsewhere as a synonym). 19 | #[derive(Clone, Debug)] 20 | pub enum Target { 21 | /// Session type: `Done`. 22 | Done, 23 | /// Session type: `Recv`. 24 | Recv(Type, Rc>), 25 | /// Session type: `Send`. 26 | Send(Type, Rc>), 27 | /// Session type: `Choose<(P, ...)>`. 28 | Choose(Vec>), 29 | /// Session type: `Offer<(P, ...)>`. 30 | Offer(Vec>), 31 | /// Session type: `Loop<...>`. 32 | Loop(Rc>), 33 | /// Session type: `Continue`. 34 | Continue(usize), 35 | /// Session type: `Split`. 36 | Split { 37 | /// The transmit-only half. 38 | tx_only: Rc>, 39 | /// The receive-only half. 40 | rx_only: Rc>, 41 | /// The continuation. 42 | cont: Rc>, 43 | }, 44 | /// Session type: `Call`. 45 | Call(Rc>, Rc>), 46 | /// Session type: `

>::Combined`. 47 | Then(Rc>, Rc>), 48 | /// Some arbitrary session type referenced by name. 49 | Type(Type), 50 | } 51 | 52 | impl fmt::Display for Target { 53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 54 | use Target::*; 55 | match self { 56 | Done => write!(f, "Done")?, 57 | Recv(t, s) => write!(f, "Recv<{}, {}>", t.to_token_stream(), s)?, 58 | Send(t, s) => write!(f, "Send<{}, {}>", t.to_token_stream(), s)?, 59 | Loop(s) => write!(f, "Loop<{}>", s)?, 60 | Split { 61 | tx_only: s, 62 | rx_only: p, 63 | cont: q, 64 | } => write!(f, "Split<{}, {}, {}>", s, p, q)?, 65 | Call(s, p) => write!(f, "Call<{}, {}>", s, p)?, 66 | Then(s, p) => write!(f, "<{} as Then<{}>>::Combined", s, p)?, 67 | Choose(cs) => { 68 | let count = cs.len(); 69 | write!(f, "Choose<(")?; 70 | for (i, c) in cs.iter().enumerate() { 71 | write!(f, "{}", c)?; 72 | if i + 1 < count { 73 | write!(f, ", ")?; 74 | } 75 | } 76 | if count == 1 { 77 | write!(f, ",")?; 78 | } 79 | write!(f, ")>")?; 80 | } 81 | Offer(cs) => { 82 | let count = cs.len(); 83 | write!(f, "Offer<(")?; 84 | for (i, c) in cs.iter().enumerate() { 85 | write!(f, "{}", c)?; 86 | if i + 1 < count { 87 | write!(f, ", ")?; 88 | } 89 | } 90 | if count == 1 { 91 | write!(f, ",")?; 92 | } 93 | write!(f, ")>")?; 94 | } 95 | Continue(n) => { 96 | write!(f, "Continue<{}>", n)?; 97 | } 98 | Type(s) => write!(f, "{}", s.to_token_stream())?, 99 | } 100 | Ok(()) 101 | } 102 | } 103 | 104 | impl Spanned { 105 | /// Convert this `Spanned` into a `TokenStream` using a provided crate name when 106 | /// referencing types from dialectic. 107 | pub fn to_token_stream_with_crate_name(&self, dialectic_crate: &Path) -> TokenStream { 108 | let mut tokens = TokenStream::new(); 109 | self.to_tokens_with_crate_name(dialectic_crate, &mut tokens); 110 | tokens 111 | } 112 | 113 | /// Convert this `Spanned` into tokens and append them to the provided `TokenStream` 114 | /// using a provided crate name when referencing types from dialectic. 115 | pub fn to_tokens_with_crate_name(&self, dialectic_crate: &Path, tokens: &mut TokenStream) { 116 | use Target::*; 117 | 118 | // We assign the associated span of the target to any type tokens generated, in an attempt 119 | // to get at least some type errors/issues to show up in the right place. 120 | let span = self.span; 121 | 122 | match &self.inner { 123 | Done => quote_spanned! {span=> #dialectic_crate::types::Done }.to_tokens(tokens), 124 | Recv(t, s) => { 125 | let s = s.to_token_stream_with_crate_name(dialectic_crate); 126 | quote_spanned!(span=> #dialectic_crate::types::Recv<#t, #s>).to_tokens(tokens); 127 | } 128 | Send(t, s) => { 129 | let s = s.to_token_stream_with_crate_name(dialectic_crate); 130 | quote_spanned!(span=> #dialectic_crate::types::Send<#t, #s>).to_tokens(tokens); 131 | } 132 | Loop(s) => { 133 | let s = s.to_token_stream_with_crate_name(dialectic_crate); 134 | quote_spanned!(span=> #dialectic_crate::types::Loop<#s>).to_tokens(tokens); 135 | } 136 | Split { 137 | tx_only: s, 138 | rx_only: p, 139 | cont: q, 140 | } => { 141 | let s = s.to_token_stream_with_crate_name(dialectic_crate); 142 | let p = p.to_token_stream_with_crate_name(dialectic_crate); 143 | let q = q.to_token_stream_with_crate_name(dialectic_crate); 144 | quote_spanned!(span=> #dialectic_crate::types::Split<#s, #p, #q>).to_tokens(tokens); 145 | } 146 | Call(s, p) => { 147 | let s = s.to_token_stream_with_crate_name(dialectic_crate); 148 | let p = p.to_token_stream_with_crate_name(dialectic_crate); 149 | quote_spanned!(span=> #dialectic_crate::types::Call<#s, #p>).to_tokens(tokens); 150 | } 151 | Then(s, p) => { 152 | let s = s.to_token_stream_with_crate_name(dialectic_crate); 153 | let p = p.to_token_stream_with_crate_name(dialectic_crate); 154 | quote_spanned!(span=> <#s as #dialectic_crate::types::Then<#p>>::Combined) 155 | .to_tokens(tokens); 156 | } 157 | Choose(cs) => { 158 | let cs = cs 159 | .iter() 160 | .map(|c| c.to_token_stream_with_crate_name(dialectic_crate)); 161 | quote_spanned!(span=> #dialectic_crate::types::Choose<(#(#cs,)*)>).to_tokens(tokens) 162 | } 163 | Offer(cs) => { 164 | let cs = cs 165 | .iter() 166 | .map(|c| c.to_token_stream_with_crate_name(dialectic_crate)); 167 | quote_spanned!(span=> #dialectic_crate::types::Offer<(#(#cs,)*)>).to_tokens(tokens) 168 | } 169 | Continue(n) => { 170 | quote_spanned!(span=> #dialectic_crate::types::Continue<#n>).to_tokens(tokens) 171 | } 172 | Type(s) => quote_spanned!(span=> #s).to_tokens(tokens), 173 | } 174 | } 175 | } 176 | 177 | impl ToTokens for Spanned { 178 | fn to_tokens(&self, tokens: &mut TokenStream) { 179 | self.to_tokens_with_crate_name(&crate::dialectic_path(), tokens); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /dialectic-compiler/tests/common.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! expect_parse { 3 | ({$($syntax:tt)*} => $output:expr) => {{ 4 | use dialectic_compiler::Invocation; 5 | 6 | let syntax = stringify!($($syntax)*); 7 | let s = syn::parse_str::(syntax) 8 | .unwrap() 9 | .compile() 10 | .to_string(); 11 | 12 | assert_eq!(s, $output); 13 | }}; 14 | } 15 | 16 | #[macro_export] 17 | macro_rules! expect_errors { 18 | ({$($syntax:tt)*} => [$($err:expr,)*]) => {{ 19 | use { 20 | dialectic_compiler::{CompileError, Invocation}, 21 | std::collections::HashSet, 22 | }; 23 | 24 | let syntax = stringify!($($syntax)*); 25 | let err_set = syn::parse_str::(syntax) 26 | .unwrap() 27 | .compile() 28 | .unwrap_err() 29 | .into_iter() 30 | .map(|err| err.to_string()) 31 | .collect::>(); 32 | let expected_errs: &[CompileError] = &[$($err),*]; 33 | let expected_set = expected_errs.iter().map(|err| err.to_string()).collect::>(); 34 | 35 | assert_eq!(err_set, expected_set, "unexpected set of errors"); 36 | }}; 37 | } 38 | -------------------------------------------------------------------------------- /dialectic-compiler/tests/misc_syntax.rs: -------------------------------------------------------------------------------- 1 | use dialectic_compiler::Invocation; 2 | 3 | mod common; 4 | 5 | #[test] 6 | fn hello_invocation() { 7 | let to_parse = " 8 | send String; 9 | recv String; 10 | "; 11 | 12 | let ast = syn::parse_str::(to_parse).unwrap(); 13 | let s = ast.compile().unwrap().to_string(); 14 | assert_eq!(s, "Send>"); 15 | } 16 | 17 | #[test] 18 | fn hello_invocation_double_block() { 19 | let to_parse = "{ 20 | send String; 21 | recv String; 22 | }"; 23 | 24 | let ast = syn::parse_str::(to_parse).unwrap(); 25 | let s = ast.compile().unwrap().to_string(); 26 | assert_eq!(s, "Send>"); 27 | } 28 | 29 | #[test] 30 | fn basic_split() { 31 | let to_parse = "split { 32 | -> send String, 33 | <- recv String, 34 | }"; 35 | 36 | let ast = syn::parse_str::(to_parse).unwrap(); 37 | let s = ast.compile().unwrap().to_string(); 38 | assert_eq!(s, "Split, Recv, Done>"); 39 | } 40 | 41 | #[test] 42 | fn continued_split() { 43 | let to_parse = "{ 44 | split { 45 | -> send String, 46 | <- recv String, 47 | }; 48 | send bool; 49 | }"; 50 | 51 | let ast = syn::parse_str::(to_parse).unwrap(); 52 | let s = ast.compile().unwrap().to_string(); 53 | assert_eq!( 54 | s, 55 | "Split, Recv, Send>" 56 | ); 57 | } 58 | 59 | #[test] 60 | fn simple_break_outside_of_loop() { 61 | expect_errors! { 62 | { 63 | break; 64 | } => [ 65 | CompileError::BreakOutsideLoop, 66 | ] 67 | }; 68 | } 69 | 70 | #[test] 71 | fn simple_continue_outside_of_loop() { 72 | expect_errors! { 73 | { 74 | continue; 75 | } => [ 76 | CompileError::ContinueOutsideLoop, 77 | ] 78 | }; 79 | } 80 | 81 | #[test] 82 | fn shadowed_label() { 83 | expect_errors! { 84 | { 85 | 'foo: loop { 86 | 'foo: loop {} 87 | } 88 | } => [ 89 | CompileError::UnproductiveLoop, 90 | CompileError::ShadowedLabel("foo".to_owned()), 91 | ] 92 | }; 93 | } 94 | 95 | #[test] 96 | fn undeclared_label() { 97 | expect_errors! { 98 | { 99 | continue 'foo; 100 | } => [ 101 | CompileError::ContinueOutsideLoop, 102 | ] 103 | }; 104 | } 105 | 106 | #[test] 107 | fn infinite_loop() { 108 | expect_errors! { 109 | { 110 | loop { 111 | send (); 112 | }; 113 | send () 114 | } => [ 115 | CompileError::UnreachableStatement, 116 | CompileError::FollowingCodeUnreachable, 117 | ] 118 | }; 119 | } 120 | 121 | #[test] 122 | fn simple_unproductive_loop() { 123 | expect_errors! { 124 | { 125 | loop {} 126 | } => [ 127 | CompileError::UnproductiveLoop, 128 | ] 129 | }; 130 | } 131 | 132 | #[test] 133 | fn nested_unproductive_loop() { 134 | expect_errors! { 135 | { 136 | 'outer: loop { 137 | loop { 138 | continue 'outer; 139 | } 140 | } 141 | } => [ 142 | CompileError::UnproductiveLoop, 143 | CompileError::UnproductiveContinue, 144 | ] 145 | }; 146 | } 147 | 148 | #[test] 149 | fn break_unproductive_loop() { 150 | expect_errors! { 151 | { 152 | loop { 153 | loop { 154 | break; 155 | } 156 | } 157 | } => [ 158 | CompileError::UnproductiveLoop, 159 | ] 160 | }; 161 | } 162 | 163 | #[test] 164 | fn very_unproductive_loop() { 165 | expect_errors! { 166 | { 167 | loop { 168 | loop { break; }; 169 | 'outer: loop { 170 | loop { 171 | break 'outer; 172 | }; 173 | send (); 174 | }; 175 | } 176 | } => [ 177 | CompileError::UnreachableStatement, 178 | CompileError::FollowingCodeUnreachable, 179 | CompileError::UnproductiveLoop, 180 | ] 181 | }; 182 | } 183 | 184 | #[test] 185 | fn loop_de_loop() { 186 | expect_errors! { 187 | { 188 | loop {} loop {} 189 | } 190 | => [ 191 | CompileError::UnproductiveLoop, 192 | CompileError::UnreachableStatement, 193 | CompileError::FollowingCodeUnreachable, 194 | ] 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /dialectic-compiler/tests/parser_roundtrip.rs: -------------------------------------------------------------------------------- 1 | use { 2 | dialectic_compiler::{Spanned, Syntax}, 3 | quickcheck::{Gen, QuickCheck, TestResult}, 4 | quote::ToTokens, 5 | }; 6 | 7 | fn parser_roundtrip_property(syntax: Spanned) -> TestResult { 8 | let syntax_tokens = syntax.to_token_stream(); 9 | let mut g = Gen::new(0); 10 | match syn::parse2::>( 11 | syntax.to_token_stream_with(&mut || *g.choose(&[true, false]).unwrap()), 12 | ) { 13 | Ok(parsed) => { 14 | TestResult::from_bool(syntax_tokens.to_string() == parsed.to_token_stream().to_string()) 15 | } 16 | Err(error) => TestResult::error(format!( 17 | "failed w/ parse string {}, error: {}", 18 | syntax_tokens, error 19 | )), 20 | } 21 | } 22 | 23 | #[test] 24 | fn parser_roundtrip() { 25 | QuickCheck::new() 26 | .gen(Gen::new(13)) 27 | .quickcheck(parser_roundtrip_property as fn(_) -> TestResult) 28 | } 29 | -------------------------------------------------------------------------------- /dialectic-compiler/tests/tally_client.rs: -------------------------------------------------------------------------------- 1 | use { 2 | dialectic_compiler::{syntax, Invocation, Spanned, Syntax}, 3 | quote::ToTokens, 4 | syn::Type, 5 | }; 6 | 7 | #[test] 8 | fn tally_client_expr_call_ast() { 9 | let client_ast: Spanned = Syntax::Loop( 10 | None, 11 | Box::new( 12 | Syntax::Choose(vec![ 13 | Syntax::Break(None).into(), 14 | Syntax::Block(vec![ 15 | Syntax::send("Operation").into(), 16 | Syntax::call(Syntax::type_("ClientTally")).into(), 17 | ]) 18 | .into(), 19 | ]) 20 | .into(), 21 | ), 22 | ) 23 | .into(); 24 | 25 | let s = format!("{}", syntax::compile(&client_ast).unwrap()); 26 | assert_eq!( 27 | s, 28 | "Loop>>)>>" 29 | ); 30 | } 31 | 32 | #[test] 33 | fn tally_client_expr_call_parse_string() { 34 | let to_parse = "loop { 35 | choose { 36 | 0 => break, 37 | 1 => { 38 | send Operation; 39 | call ClientTally; 40 | }, 41 | } 42 | }"; 43 | 44 | let ast = syn::parse_str::>(to_parse).unwrap(); 45 | let s = format!("{}", syntax::compile(&ast).unwrap()); 46 | assert_eq!( 47 | s, 48 | "Loop>>)>>" 49 | ); 50 | } 51 | 52 | #[test] 53 | fn tally_client_invocation_call_parse_string() { 54 | let to_parse = "loop { 55 | choose { 56 | 0 => break, 57 | 1 => { 58 | send Operation; 59 | call ClientTally; 60 | }, 61 | } 62 | }"; 63 | 64 | let ast = syn::parse_str::>(to_parse).unwrap(); 65 | let s = format!("{}", syntax::compile(&ast).unwrap()); 66 | assert_eq!( 67 | s, 68 | "Loop>>)>>" 69 | ); 70 | } 71 | 72 | #[test] 73 | fn tally_client_invocation_direct_subst_parse_string() { 74 | let to_parse = "'client: loop { 75 | choose { 76 | 0 => break, 77 | 1 => { 78 | send Operation; 79 | ClientTally; 80 | }, 81 | } 82 | }"; 83 | 84 | let ast = syn::parse_str::>(to_parse).unwrap(); 85 | let s = format!("{}", syntax::compile(&ast).unwrap()); 86 | assert_eq!( 87 | s, 88 | "Loop>>::Combined>)>>" 89 | ); 90 | } 91 | 92 | #[test] 93 | fn tally_client_direct_subst_nested_loop_break() { 94 | let to_parse = "'client: loop { 95 | choose { 96 | 0 => break, 97 | 1 => { 98 | send Operation; 99 | loop { 100 | choose { 101 | 0 => send i64, 102 | 1 => { 103 | recv i64; 104 | continue 'client; 105 | } 106 | } 107 | } 108 | }, 109 | } 110 | }"; 111 | 112 | let lhs: Type = syn::parse2( 113 | syn::parse_str::(to_parse) 114 | .unwrap() 115 | .compile() 116 | .unwrap() 117 | .into_token_stream(), 118 | ) 119 | .unwrap(); 120 | 121 | let rhs: Type = syn::parse_str( 122 | "::dialectic::types::Loop< 123 | ::dialectic::types::Choose<( 124 | ::dialectic::types::Done, 125 | ::dialectic::types::Send< 126 | Operation, 127 | ::dialectic::types::Loop< 128 | ::dialectic::types::Choose<( 129 | ::dialectic::types::Send>, 130 | ::dialectic::types::Recv>, 131 | )> 132 | > 133 | >, 134 | )> 135 | >", 136 | ) 137 | .unwrap(); 138 | 139 | assert_eq!( 140 | lhs.to_token_stream().to_string(), 141 | rhs.to_token_stream().to_string() 142 | ); 143 | } 144 | -------------------------------------------------------------------------------- /dialectic-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dialectic-macro" 3 | version = "0.1.0" 4 | authors = ["Shea Leffler ", "Kenny Foner "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Procedural macros used by and exported from the Dialectic crate" 8 | repository = "https://github.com/boltlabs-inc/dialectic" 9 | homepage = "https://github.com/boltlabs-inc/dialectic" 10 | keywords = ["session", "types", "async", "channel", "macro"] 11 | categories = ["asynchronous", "concurrency"] 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | dialectic-compiler = { version = "0.1", path = "../dialectic-compiler" } 18 | syn = { version = "1.0", features = ["full", "parsing"] } 19 | proc-macro2 = "1.0" 20 | quote = "1.0" 21 | 22 | [dev-dependencies] 23 | dialectic = { version = "0.4", path = "../dialectic" } 24 | dialectic-tokio-mpsc = { version = "0.1", path = "../dialectic-tokio-mpsc" } 25 | static_assertions = "1.1" 26 | tokio = "1.2" 27 | 28 | [package.metadata.docs.rs] 29 | all-features = true 30 | rustdoc-args = ["--cfg", "docsrs"] -------------------------------------------------------------------------------- /dialectic-macro/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /dialectic-macro/README.md: -------------------------------------------------------------------------------- 1 | [![Rust](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml/badge.svg)](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml) 2 | ![license: MIT](https://img.shields.io/github/license/boltlabs-inc/dialectic) 3 | [![crates.io](https://img.shields.io/crates/v/dialectic-macro)](https://crates.io/crates/dialectic-macro) 4 | [![docs.rs documentation](https://docs.rs/dialectic-macro/badge.svg)](https://docs.rs/dialectic-macro) 5 | 6 | This crate contains the `Session!`, `Transmitter`, and `Receiver` macros for use in [Dialectic](https://crates.io/crates/dialectic), as 7 | well as several other macros which are used internally to Dialectic. The `dialectic-macro` crate is 8 | considered an internal implementation detail and should *never* be relied upon or depended on 9 | outside of the `dialectic` crate itself, which re-exports all the important user-facing procedural 10 | macros defined in this crate. 11 | 12 | ## For contributors 13 | 14 | Internally, `dialectic-macro` is used to define large swathes of trait definitions which cover const 15 | generics and conversions between const generics and unary type-level representations. This is due to 16 | current limitations of const generics, and may be replaced in the future. Otherwise, 17 | `dialectic-macro` is the main dependent of the `dialectic-compiler` crate; its functionality is used 18 | within `dialectic-macro` to implement the `Session!` proc macro, and nowhere else. 19 | -------------------------------------------------------------------------------- /dialectic-macro/tests/loop_break_scope.rs: -------------------------------------------------------------------------------- 1 | use dialectic::prelude::*; 2 | use dialectic::types::*; 3 | use static_assertions::assert_type_eq_all; 4 | 5 | #[allow(dead_code)] 6 | type Bug = Session! { 7 | 'outer: loop { 8 | loop { 9 | break; 10 | }; 11 | send (); 12 | } 13 | }; 14 | 15 | assert_type_eq_all!(Bug, Loop>>>,); 16 | -------------------------------------------------------------------------------- /dialectic-macro/tests/loop_choose_empty.rs: -------------------------------------------------------------------------------- 1 | use dialectic::{prelude::*, types::*}; 2 | use static_assertions::assert_type_eq_all; 3 | 4 | #[allow(dead_code)] 5 | type Bug = Session! { 6 | loop { 7 | choose { 8 | 0 => {}, 9 | } 10 | } 11 | }; 12 | 13 | assert_type_eq_all!(Bug, Loop,)>>,); 14 | -------------------------------------------------------------------------------- /dialectic-macro/tests/lots_of_jumps.rs: -------------------------------------------------------------------------------- 1 | use dialectic::prelude::*; 2 | use dialectic::types::*; 3 | use static_assertions::assert_type_eq_all; 4 | 5 | #[allow(dead_code)] 6 | type LabelExample = Session! { 7 | 'outer: loop { 8 | send i64; 9 | loop { 10 | recv bool; 11 | offer { 12 | 0 => break 'outer, 13 | 1 => continue 'outer, 14 | 2 => break, 15 | 3 => continue, 16 | 4 => send String, 17 | }; 18 | send bool; 19 | }; 20 | recv i64; 21 | } 22 | }; 23 | 24 | assert_type_eq_all!( 25 | LabelExample, 26 | Loop< 27 | Send< 28 | i64, 29 | Loop< 30 | Recv< 31 | bool, 32 | Offer<( 33 | Done, 34 | Continue<1>, 35 | Recv>, 36 | Continue<0>, 37 | Send>> 38 | )>, 39 | >, 40 | >, 41 | >, 42 | >, 43 | ); 44 | -------------------------------------------------------------------------------- /dialectic-macro/tests/optional_terminators.rs: -------------------------------------------------------------------------------- 1 | use dialectic::prelude::*; 2 | use dialectic::types::*; 3 | use static_assertions::assert_type_eq_all; 4 | 5 | struct Foo; 6 | 7 | #[allow(dead_code)] 8 | type BigChoose = Session! { 9 | choose { 10 | 0 => send (), 11 | 1 => { send () } 12 | 2 => call { Foo } 13 | 3 => call Foo, 14 | 4 => split { 15 | -> { 16 | send String; 17 | } 18 | <- recv String 19 | } 20 | } 21 | }; 22 | 23 | assert_type_eq_all!( 24 | BigChoose, 25 | Choose<( 26 | Send<(), Done>, 27 | Send<(), Done>, 28 | Call, 29 | Call, 30 | Split, Recv, Done> 31 | )> 32 | ); 33 | -------------------------------------------------------------------------------- /dialectic-null/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dialectic-null" 3 | version = "0.1.0" 4 | authors = ["Kenny Foner ", "Shea Leffler "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A no-op backend for the Dialectic crate, intended for benchmarking and testing" 8 | repository = "https://github.com/boltlabs-inc/dialectic" 9 | homepage = "https://github.com/boltlabs-inc/dialectic" 10 | keywords = ["session", "types", "async", "channel", "protocol"] 11 | categories = ["asynchronous", "concurrency"] 12 | 13 | [dependencies] 14 | dialectic = { version = "0.4", path = "../dialectic" } 15 | 16 | [package.metadata.docs.rs] 17 | all-features = true 18 | rustdoc-args = ["--cfg", "docsrs"] -------------------------------------------------------------------------------- /dialectic-null/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /dialectic-null/README.md: -------------------------------------------------------------------------------- 1 | [![Rust](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml/badge.svg)](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml) 2 | ![license: MIT](https://img.shields.io/github/license/boltlabs-inc/dialectic-null) 3 | [![crates.io](https://img.shields.io/crates/v/dialectic-null)](https://crates.io/crates/dialectic-null) 4 | [![docs.rs documentation](https://docs.rs/dialectic-null/badge.svg)](https://docs.rs/dialectic-null) 5 | 6 | This crate contains the "null" backend for [Dialectic](https://crates.io/crates/dialectic). If you 7 | are a user, you will likely never have a use for this, as what it does is completely eliminate any 8 | transport backend *and* it can only send and receive the unit type `()`. The null backend is used 9 | within Dialectic for testing and benchmarking purposes, for example to determine how much overhead 10 | Dialectic's constructs have over raw operations. -------------------------------------------------------------------------------- /dialectic-null/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A "null" backend implementation for the [`dialectic`] crate which can only send and receive the unit type `()`. 2 | //! 3 | //! This backend is useful primarily only for benchmarking, as it does the absolute minimum amount 4 | //! of work, so that it is easier to isolate performance issues in Dialectic itself. You cannot 5 | //! implement most protocols using this backend, as it is limited to transporting the unit type `()` 6 | //! and cannot [`choose`](dialectic::Chan::choose) or [`offer!`](dialectic::offer) more than a 7 | //! single choice. 8 | 9 | #![allow(clippy::type_complexity)] 10 | #![warn(missing_docs)] 11 | #![warn(missing_copy_implementations, missing_debug_implementations)] 12 | #![warn(unused_qualifications, unused_results)] 13 | #![warn(future_incompatible)] 14 | #![warn(unused)] 15 | // Documentation configuration 16 | #![forbid(broken_intra_doc_links)] 17 | 18 | use dialectic::backend::{self, By, Choice, Mut, Ref, Val}; 19 | use std::{convert::TryInto, future::Future, pin::Pin}; 20 | 21 | /// Shorthand for a [`Chan`](dialectic::Chan) using a null [`Sender`] and [`Receiver`]. 22 | /// 23 | /// # Examples 24 | /// 25 | /// ``` 26 | /// use dialectic::prelude::*; 27 | /// use dialectic::types::Done; 28 | /// use dialectic_null as null; 29 | /// 30 | /// let _: (null::Chan, null::Chan) = 31 | /// Done::channel(null::channel); 32 | /// ``` 33 | pub type Chan

= dialectic::Chan; 34 | 35 | /// A receiver only capable of receiving the unit type `()`. 36 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 37 | pub struct Receiver { 38 | _private: (), 39 | } 40 | 41 | /// A sender only capable of sending the unit type `()`. 42 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 43 | pub struct Sender { 44 | _private: (), 45 | } 46 | 47 | /// Create a channel for transporting unit values `()` only. 48 | /// 49 | /// # Examples 50 | /// 51 | /// ``` 52 | /// let (tx, rx) = dialectic_null::channel(); 53 | /// ``` 54 | pub fn channel() -> (Sender, Receiver) { 55 | (Sender::default(), Receiver::default()) 56 | } 57 | 58 | /// An error thrown while receiving from or sending to a null channel. 59 | /// 60 | /// No such errors are possible, so this type cannot be constructed. 61 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 62 | pub struct Error { 63 | _private: (), 64 | } 65 | 66 | impl std::fmt::Display for Error { 67 | fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 68 | Ok(()) 69 | } 70 | } 71 | 72 | impl std::error::Error for Error {} 73 | 74 | impl backend::Transmitter for Sender { 75 | type Error = Error; 76 | 77 | fn send_choice<'async_lifetime, const LENGTH: usize>( 78 | &'async_lifetime mut self, 79 | _choice: Choice, 80 | ) -> Pin> + Send + 'async_lifetime>> { 81 | Box::pin(async { Ok(()) }) 82 | } 83 | } 84 | 85 | impl backend::Transmit<(), Val> for Sender { 86 | fn send<'a, 'async_lifetime>( 87 | &'async_lifetime mut self, 88 | _message: <() as By<'a, Val>>::Type, 89 | ) -> Pin> + Send + 'async_lifetime>> 90 | where 91 | (): By<'a, Val>, 92 | 'a: 'async_lifetime, 93 | { 94 | Box::pin(async { Ok(()) }) 95 | } 96 | } 97 | 98 | impl backend::Transmit<(), Ref> for Sender { 99 | fn send<'a, 'async_lifetime>( 100 | &'async_lifetime mut self, 101 | _message: <() as By<'a, Ref>>::Type, 102 | ) -> Pin> + Send + 'async_lifetime>> 103 | where 104 | (): By<'a, Ref>, 105 | 'a: 'async_lifetime, 106 | { 107 | Box::pin(async { Ok(()) }) 108 | } 109 | } 110 | 111 | impl backend::Transmit<(), Mut> for Sender { 112 | fn send<'a, 'async_lifetime>( 113 | &'async_lifetime mut self, 114 | _message: <() as By<'a, Mut>>::Type, 115 | ) -> Pin> + Send + 'async_lifetime>> 116 | where 117 | (): By<'a, Mut>, 118 | 'a: 'async_lifetime, 119 | { 120 | Box::pin(async { Ok(()) }) 121 | } 122 | } 123 | 124 | impl backend::Transmit> for Sender { 125 | fn send<'a, 'async_lifetime>( 126 | &'async_lifetime mut self, 127 | _message: as By>::Type, 128 | ) -> Pin> + Send + 'async_lifetime>> 129 | where 130 | 'a: 'async_lifetime, 131 | { 132 | Box::pin(async { Ok(()) }) 133 | } 134 | } 135 | 136 | impl backend::Receiver for Receiver { 137 | type Error = Error; 138 | 139 | fn recv_choice<'async_lifetime, const LENGTH: usize>( 140 | &'async_lifetime mut self, 141 | ) -> Pin, Self::Error>> + Send + 'async_lifetime>> 142 | { 143 | Box::pin(async { 0.try_into().map_err(|_| Error { _private: () }) }) 144 | } 145 | } 146 | 147 | impl backend::Receive<()> for Receiver { 148 | fn recv<'async_lifetime>( 149 | &'async_lifetime mut self, 150 | ) -> Pin> + Send + 'async_lifetime>> { 151 | Box::pin(async { Ok(()) }) 152 | } 153 | } 154 | 155 | impl backend::Receive> for Receiver { 156 | fn recv<'async_lifetime>( 157 | &'async_lifetime mut self, 158 | ) -> Pin, Self::Error>> + Send + 'async_lifetime>> 159 | { 160 | Box::pin(async { 0.try_into().map_err(|_| Error { _private: () }) }) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /dialectic-reconnect/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dialectic-reconnect" 3 | version = "0.1.0" 4 | authors = ["Kenny Foner "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "An adapter for the Dialectic crate allowing any backend to transparently retry and resume temporarily broken connections" 8 | repository = "https://github.com/boltlabs-inc/dialectic" 9 | homepage = "https://github.com/boltlabs-inc/dialectic" 10 | keywords = ["session", "types", "async", "channel", "protocol"] 11 | categories = ["asynchronous", "concurrency"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | dialectic = { version = "0.4", path = "../dialectic" } 17 | call-by = "^0.2.3" 18 | tokio = { version = "^1.5", features = ["sync", "time"] } 19 | dashmap = "4" 20 | rand = "0.8" 21 | derivative = "2" 22 | serde = { version = "1", optional = true, default-features = false, features = ["derive"] } 23 | humantime-serde = { version = "1", optional = true } 24 | 25 | [dev-dependencies] 26 | tokio = { version = "1", features = ["full"]} 27 | dialectic-tokio-serde = { version = "0.1", path = "../dialectic-tokio-serde" } 28 | dialectic-tokio-serde-json = { version = "0.1", path = "../dialectic-tokio-serde-json" } 29 | anyhow = "1" 30 | futures = { version = "0.3", features = ["std"], default-features = false } 31 | 32 | [package.metadata.docs.rs] 33 | all-features = true 34 | rustdoc-args = ["--cfg", "docsrs"] -------------------------------------------------------------------------------- /dialectic-reconnect/examples/ping.rs: -------------------------------------------------------------------------------- 1 | // This client is meant to be paired with the `pong` server. It implements a very simple protocol 2 | // where it sends a `()` and receives a `()`, at regularly spaced intervals. However, the connection 3 | // to the server can be interrupted and it will continue to function correctly, transparently 4 | // retrying the connection with no intervention from the code written in terms of the channel. 5 | // 6 | // To test this program, run `cargo run --example ping` and `cargo run --example pong` 7 | // simultaneously. You can then use the `tcpkill` program to perform a denial of service attack on 8 | // the port through which the two programs communicate, using this command: 9 | // 10 | // ``` 11 | // sudo tcpkill -i lo -1 ip host 127.0.0.1 and port 5000 12 | // ``` 13 | // 14 | // Notice that both the client and server log errors, but do not crash. Varying the degree of 15 | // aggressiveness in the call to `tcpkill` and the interval for `ping` may change the effectiveness 16 | // of the denial of service attack, which may result in no connections being able to succeed. 17 | 18 | use anyhow::Error; 19 | use dialectic::prelude::*; 20 | use dialectic_reconnect::{ 21 | retry::{self, Connector, Recovery}, 22 | Backoff, 23 | }; 24 | use dialectic_tokio_serde::codec::LinesCodec; 25 | use dialectic_tokio_serde_json::{self as json, Json}; 26 | use std::{ 27 | convert::TryFrom, 28 | net::IpAddr, 29 | time::{Duration, Instant}, 30 | }; 31 | use tokio::{ 32 | net::{ 33 | tcp::{OwnedReadHalf, OwnedWriteHalf}, 34 | TcpStream, 35 | }, 36 | sync::mpsc, 37 | }; 38 | 39 | // The handshake session type 40 | pub type Handshake = Session! { 41 | choose { 42 | // Start a new session: receive a session key from the server 43 | 0 => recv usize, 44 | // Resume an existing session: submit a session key to the server 45 | 1 => send usize, 46 | }; 47 | }; 48 | 49 | // The protocol we will be running: just send and receive in a loop forever 50 | pub type PingPong = Session! { 51 | loop { 52 | send (); 53 | recv (); 54 | } 55 | }; 56 | 57 | // The specific backend types we'll be using in this example 58 | pub type Tx = dialectic_tokio_serde::Sender; 59 | pub type Rx = dialectic_tokio_serde::Receiver; 60 | 61 | #[allow(unused)] 62 | #[tokio::main] 63 | async fn main() -> Result<(), Error> { 64 | // How to connect to an address and generate a `Tx`/`Rx` pair 65 | async fn connect(addr: (IpAddr, u16)) -> Result<(Tx, Rx), Error> { 66 | let (rx, tx) = TcpStream::connect(addr).await?.into_split(); 67 | Ok(json::lines(tx, rx, 1024 * 8)) 68 | } 69 | 70 | // How to perform the initial handshake, receiving a session key from the server 71 | async fn init(chan: Chan) -> Result { 72 | Ok(chan.choose::<0>().await?.recv().await?.0) 73 | } 74 | 75 | // How to perform a retry handshake, submitting a session key to the server 76 | async fn retry(key: usize, chan: Chan) -> Result<(), Error> { 77 | let chan = chan.choose::<1>().await?; 78 | chan.send(key).await?.close(); 79 | Ok(()) 80 | } 81 | 82 | // To prevent logging from interfering with retrying, we move logging to a separate task 83 | let (log, mut log_recv) = mpsc::unbounded_channel(); 84 | 85 | // Run a task to print all logged messages to stderr 86 | tokio::spawn(async move { 87 | while let Some(message) = log_recv.recv().await { 88 | eprintln!("{}", message); 89 | } 90 | }); 91 | 92 | // The backoff strategy used during error recovery 93 | fn backoff(retries: usize, error: &E) -> retry::Recovery { 94 | Backoff::with_delay(Duration::from_millis(100)) 95 | // .exponential(2.0) 96 | .jitter(Duration::from_millis(10)) 97 | .max_delay(Some(Duration::from_secs(1))) 98 | .build(Recovery::ReconnectAfter)(retries, error) 99 | } 100 | 101 | // A connector for our protocol, with a 10 second timeout, which logs all errors and attempts to 102 | // recover using the backoff strategy for every kind of error 103 | let (key, mut chan) = Connector::new(connect, init, retry, PingPong::default()) 104 | .timeout(Some(Duration::from_secs(10))) 105 | .recover_connect({ 106 | let log = log.clone(); 107 | move |tries, error| { 108 | let _ = log.send(format!( 109 | "[reconnect error] retries: {}, error: {}", 110 | tries, error 111 | )); 112 | backoff(tries, error) 113 | } 114 | }) 115 | .recover_handshake({ 116 | let log = log.clone(); 117 | move |tries, error| { 118 | let _ = log.send(format!( 119 | "[handshake error] retries: {}, error: {}", 120 | tries, error 121 | )); 122 | backoff(tries, error) 123 | } 124 | }) 125 | .recover_tx({ 126 | let log = log.clone(); 127 | move |tries, error| { 128 | let _ = log.send(format!("[TX error] retries: {}, error: {}", tries, error)); 129 | backoff(tries, error) 130 | } 131 | }) 132 | .recover_rx({ 133 | let log = log.clone(); 134 | move |tries, error| { 135 | let _ = log.send(format!("[RX error] retries: {}, error: {}", tries, error)); 136 | backoff(tries, error) 137 | } 138 | }) 139 | .connect((IpAddr::try_from([127, 0, 0, 1]).unwrap(), 5000)) 140 | .await?; 141 | 142 | println!("[{}] create session", key); 143 | 144 | // Loop forever measuring how long it takes for the server to respond 145 | let mut n: usize = 0; 146 | loop { 147 | let start = Instant::now(); 148 | println!("[{}] {}: ping ...", key, n); 149 | chan = chan.send(()).await?.recv().await?.1; 150 | println!("[{}] ... pong (elapsed: {:?})", key, start.elapsed()); 151 | tokio::time::sleep(Duration::from_millis(100)).await; 152 | n += 1; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /dialectic-reconnect/examples/pong.rs: -------------------------------------------------------------------------------- 1 | // This server is meant to be paired with the `ping` client. It implements a very simple protocol 2 | // where it receives a `()` and then sends a `()`, at regularly spaced intervals. However, the 3 | // connection to the client can be interrupted and it will continue to function correctly, 4 | // transparently resuming the connection when the client retries, with no intervention from the code 5 | // written in terms of the channel. 6 | // 7 | // To test this program, run `cargo run --example ping` and `cargo run --example pong` 8 | // simultaneously. You can then use the `tcpkill` program to perform a denial of service attack on 9 | // the port through which the two programs communicate, using this command: 10 | // 11 | // ``` 12 | // sudo tcpkill -i lo -1 ip host 127.0.0.1 and port 5000 13 | // ``` 14 | // 15 | // Notice that both the client and server log errors, but do not crash. Varying the degree of 16 | // aggressiveness in the call to `tcpkill` and the interval for `ping` may change the effectiveness 17 | // of the denial of service attack, which may result in no connections being able to succeed. 18 | 19 | use anyhow::Error; 20 | use dialectic::prelude::*; 21 | use dialectic_reconnect::resume::{Acceptor, Recovery, ResumeKind}; 22 | use dialectic_tokio_serde_json as json; 23 | use futures::{stream::FuturesUnordered, FutureExt, StreamExt}; 24 | use std::{ 25 | convert::TryFrom, 26 | net::IpAddr, 27 | sync::{ 28 | atomic::{AtomicUsize, Ordering}, 29 | Arc, 30 | }, 31 | time::{Duration, Instant}, 32 | }; 33 | use tokio::{net::TcpListener, select, sync::mpsc}; 34 | 35 | mod ping; 36 | type PongPing = ::Dual; 37 | type Handshake = ::Dual; 38 | use ping::{Rx, Tx}; 39 | 40 | #[tokio::main] 41 | async fn main() -> Result<(), Error> { 42 | // We assign keys to sessions based on sequential ordering 43 | let key = Arc::new(AtomicUsize::new(0)); 44 | 45 | // The handshake either generates a new key and sends it, or receives an existing key 46 | let handshake = move |chan: Chan| { 47 | let key = key.clone(); 48 | async move { 49 | let output = offer!(in chan { 50 | 0 => { 51 | // Client wants to start a new session 52 | let new_key = key.fetch_add(1, Ordering::SeqCst); 53 | chan.send(new_key).await?.close(); 54 | (ResumeKind::New, new_key) 55 | }, 56 | 1 => { 57 | // Client wants to resume an existing session 58 | let (existing_key, chan) = chan.recv().await?; 59 | chan.close(); 60 | (ResumeKind::Existing, existing_key) 61 | } 62 | })?; 63 | Ok::<_, Error>(output) 64 | } 65 | }; 66 | 67 | // To prevent logging from interfering with retrying, we move logging to a separate task 68 | let (log, mut log_recv) = mpsc::unbounded_channel(); 69 | 70 | // Create an acceptor for our protocol which times out at 10 seconds and logs errors 71 | let mut acceptor = Acceptor::new(handshake, PongPing::default()); 72 | acceptor 73 | .timeout(Some(Duration::from_secs(10))) 74 | .recover_tx({ 75 | let log = log.clone(); 76 | move |retries, error| { 77 | let _ = log.send(format!("[TX error] retries: {}, error: {}", retries, error)); 78 | Recovery::Reconnect 79 | } 80 | }) 81 | .recover_rx({ 82 | let log = log.clone(); 83 | move |retries, error| { 84 | let _ = log.send(format!("[RX error] retries: {}, error: {}", retries, error)); 85 | Recovery::Reconnect 86 | } 87 | }); 88 | 89 | // Initialize a TCP listener 90 | let listener = TcpListener::bind((IpAddr::try_from([127, 0, 0, 1]).unwrap(), 5000)).await?; 91 | 92 | // Keep track of all the pending tasks 93 | let mut tasks = FuturesUnordered::new(); 94 | 95 | // Loop forever accepting connections 96 | loop { 97 | // Concurrently do all of: 98 | select! { 99 | // Report on a completed server task 100 | Some((key, result)) = tasks.next() => { 101 | match result { 102 | Ok(Ok::<_, Error>(())) => println!("[{}] complete session", key), 103 | Ok(Err(error)) => eprintln!("[{}] unrecovered error: {}", key, error), 104 | Err(panic) => eprintln!("[{}] panic in task: {}", key, panic), 105 | } 106 | }, 107 | // Report a log message 108 | Some(message) = log_recv.recv() => eprintln!("{}", message), 109 | // Accept a new connection to the server 110 | Ok((tcp_stream, _)) = listener.accept() => { 111 | let (rx, tx) = tcp_stream.into_split(); 112 | let (tx, rx) = json::lines(tx, rx, 1024 * 8); 113 | 114 | match acceptor.accept(tx, rx).await { 115 | Err(error) => eprintln!("[accept error] {}", error), 116 | Ok((key, None)) => println!("[{}] resume session", key), 117 | Ok((key, Some(mut chan))) => { 118 | println!("[{}] create session", key); 119 | tasks.push(tokio::spawn(async move { 120 | let mut n: usize = 0; 121 | loop { 122 | let start = Instant::now(); 123 | println!("[{}] {}: pong ...", key, n); 124 | chan = chan.recv().await?.1.send(()).await?; 125 | println!("[{}] ... ping (elapsed: {:?})", key, start.elapsed()); 126 | n += 1; 127 | } 128 | }).map(move |result| (key, result))); 129 | } 130 | } 131 | }, 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /dialectic-reconnect/src/backoff.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | #[cfg(feature = "serde")] 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// A description of a backoff strategy with optional exponential delay, random jitter, maximum 7 | /// delay, and maximum retries. This can be used to generate retry strategies for building 8 | /// [`Connector`](crate::retry::Connector)s. 9 | #[derive(Debug, Clone, Copy)] 10 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 11 | #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 12 | pub struct Backoff { 13 | #[cfg_attr( 14 | all(feature = "serde", feature = "humantime_serde"), 15 | serde(with = "humantime_serde") 16 | )] 17 | initial_delay: Duration, 18 | #[cfg_attr(feature = "serde", serde(default = "defaults::exponent"))] 19 | exponent: f64, 20 | #[cfg_attr(feature = "serde", serde(default = "defaults::jitter"))] 21 | #[cfg_attr( 22 | all(feature = "serde", feature = "humantime_serde"), 23 | serde(with = "humantime_serde") 24 | )] 25 | jitter: Duration, 26 | #[cfg_attr(feature = "serde", serde(default = "defaults::max_delay"))] 27 | #[cfg_attr( 28 | all(feature = "serde", feature = "humantime_serde"), 29 | serde(with = "humantime_serde") 30 | )] 31 | max_delay: Option, 32 | #[cfg_attr(feature = "serde", serde(default = "defaults::max_retries"))] 33 | max_retries: usize, 34 | } 35 | 36 | /// The default settings for the optional parameters, defined here as functions so they can be used 37 | /// in the serde implementation as well as the builder. 38 | mod defaults { 39 | use std::time::Duration; 40 | 41 | pub const fn exponent() -> f64 { 42 | 1.0 43 | } 44 | 45 | pub const fn jitter() -> Duration { 46 | Duration::from_secs(0) 47 | } 48 | 49 | pub const fn max_delay() -> Option { 50 | None 51 | } 52 | 53 | pub const fn max_retries() -> usize { 54 | usize::MAX 55 | } 56 | } 57 | 58 | impl Backoff { 59 | /// Create a simple [`Backoff`] which delays by `initial_delay` each time it is invoked, 60 | /// forever. 61 | pub fn with_delay(initial_delay: Duration) -> Self { 62 | Backoff { 63 | initial_delay, 64 | exponent: defaults::exponent(), 65 | jitter: defaults::jitter(), 66 | max_delay: defaults::max_delay(), 67 | max_retries: defaults::max_retries(), 68 | } 69 | } 70 | 71 | /// Add an exponential factor to a [`Backoff`], so that every time it is invoked, it delays for 72 | /// that multiple of its previous delay time. 73 | pub fn exponential(&mut self, factor: f64) -> &mut Self { 74 | self.exponent = factor; 75 | self 76 | } 77 | 78 | /// Add random jitter to a [`Backoff`], so that every time it is invoked, it adds or subtracts a 79 | /// random duration from its delay within the range specified. 80 | pub fn jitter(&mut self, jitter: Duration) -> &mut Self { 81 | self.jitter = jitter; 82 | self 83 | } 84 | 85 | /// Cap the maximum delay of a [`Backoff`] so that every time it is invoked, it will delay by at 86 | /// most `max_delay`, if otherwise it would delay more. 87 | pub fn max_delay(&mut self, max_delay: Option) -> &mut Self { 88 | self.max_delay = max_delay; 89 | self 90 | } 91 | 92 | /// Set the maximum number of retries for a [`Backoff`], so that it will not retry after the 93 | /// specified number of attempts. 94 | pub fn max_retries(&mut self, max_retries: usize) -> &mut Self { 95 | self.max_retries = max_retries; 96 | self 97 | } 98 | 99 | /// Use this [`Backoff`] to generate a closure that implements the backoff strategy it 100 | /// describes, given a constructor for some kind of delay strategy parameterized by a 101 | /// `Duration`. 102 | /// 103 | /// This assumes that the [`default`](Default::default) for the given `Strategy` type represents 104 | /// failure. 105 | pub fn build( 106 | &self, 107 | strategy: impl Fn(Duration) -> Strategy + Sync + Send + 'static, 108 | ) -> impl Fn(usize, &Error) -> Strategy + Sync + Send + 'static 109 | where 110 | Strategy: Default + Sync + Send + 'static, 111 | { 112 | let initial_delay = self.initial_delay; 113 | let factor = self.exponent; 114 | let max_retries = self.max_retries; 115 | let jitter = self.jitter; 116 | let max_delay = self.max_delay; 117 | 118 | move |retries, _| { 119 | if retries <= max_retries { 120 | let mut delay = 121 | initial_delay.mul_f64(factor.abs().powf(retries.min(max_retries) as f64)); 122 | delay = max_delay.unwrap_or(delay).min(delay); 123 | let random_jitter = jitter.mul_f64(rand::random()); 124 | if rand::random() { 125 | delay += random_jitter; 126 | } else { 127 | delay -= random_jitter.min(delay); 128 | } 129 | strategy(delay) 130 | } else { 131 | Strategy::default() 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /dialectic-reconnect/src/maybe_bounded.rs: -------------------------------------------------------------------------------- 1 | //! This module defines two enums, [`Sender`] and [`Receiver`], which wrap either a bounded or 2 | //! unbounded Tokio sender or receiver, respectively. 3 | //! 4 | //! These types implement the subset of the sender/receiver API necessary for this crate, not the 5 | //! full API, most of which is not used. 6 | use tokio::sync::mpsc::{ 7 | self, 8 | error::{SendError, TrySendError}, 9 | }; 10 | 11 | /// A Tokio [`mpsc`] sender that could be either bounded or unbounded at runtime. 12 | #[derive(Debug, Clone)] 13 | pub enum Sender { 14 | Bounded(mpsc::Sender), 15 | Unbounded(mpsc::UnboundedSender), 16 | } 17 | 18 | /// A Tokio [`mpsc`] receiver that could be either bounded or unbounded at runtime. 19 | #[derive(Debug)] 20 | pub enum Receiver { 21 | Bounded(mpsc::Receiver), 22 | Unbounded(mpsc::UnboundedReceiver), 23 | } 24 | 25 | /// Create a Tokio [`mpsc`] sender/receiver pair that is either bounded or unbounded, depending on 26 | /// whether a buffer size is specified. 27 | pub fn channel(buffer: Option) -> (Sender, Receiver) { 28 | if let Some(buffer) = buffer { 29 | let (tx, rx) = mpsc::channel(buffer); 30 | (Sender::Bounded(tx), Receiver::Bounded(rx)) 31 | } else { 32 | let (tx, rx) = mpsc::unbounded_channel(); 33 | (Sender::Unbounded(tx), Receiver::Unbounded(rx)) 34 | } 35 | } 36 | 37 | impl Sender { 38 | /// Return the capacity of the underlying channel, if it is bounded, or `usize::MAX` if it is 39 | /// not bounded. 40 | pub fn capacity(&self) -> usize { 41 | match self { 42 | Sender::Bounded(tx) => tx.capacity(), 43 | Sender::Unbounded(_) => usize::MAX, 44 | } 45 | } 46 | 47 | /// Check if there is an existing receiver for this channel. 48 | pub fn is_closed(&self) -> bool { 49 | match self { 50 | Sender::Bounded(tx) => tx.is_closed(), 51 | Sender::Unbounded(tx) => tx.is_closed(), 52 | } 53 | } 54 | 55 | /// Try to send a message over the channel, returning an error if the channel is full or closed. 56 | pub fn try_send(&self, message: T) -> Result<(), TrySendError> { 57 | match self { 58 | Sender::Bounded(tx) => tx.try_send(message), 59 | Sender::Unbounded(tx) => tx 60 | .send(message) 61 | .map_err(|SendError(t)| TrySendError::Closed(t)), 62 | } 63 | } 64 | } 65 | 66 | impl Receiver { 67 | /// Receive the next value for this receiver, returning `None` if all `Sender` halves have 68 | /// dropped. 69 | pub async fn recv(&mut self) -> Option { 70 | match self { 71 | Receiver::Bounded(rx) => rx.recv().await, 72 | Receiver::Unbounded(rx) => rx.recv().await, 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /dialectic-reconnect/src/resume/end.rs: -------------------------------------------------------------------------------- 1 | use dialectic::backend; 2 | use std::{ 3 | hash::Hash, 4 | sync::{atomic::Ordering, Arc}, 5 | time::Duration, 6 | }; 7 | use tokio::time::Instant; 8 | 9 | use super::{Managed, Recovery}; 10 | use crate::maybe_bounded; 11 | use crate::util::timeout_at_option; 12 | 13 | /// Both sending and receiving ends have shared recovery logic; this struct is used for both ends. 14 | #[derive(derivative::Derivative)] 15 | #[derivative(Debug)] 16 | pub(super) struct End 17 | where 18 | Key: Eq + Hash, 19 | { 20 | key: Key, 21 | inner: Option, 22 | next: maybe_bounded::Receiver, 23 | #[derivative(Debug = "ignore")] 24 | recover: Arc Recovery + Sync + Send>, 25 | managed: Arc>, 26 | timeout: Option, 27 | } 28 | 29 | /// A resuming sender end is an [`End`] whose `Inner` is `Tx` and whose `Error` is a `Tx::Error`. 30 | pub(super) type SenderEnd = End::Error, Tx, Tx, Rx>; 31 | 32 | /// A resuming receiver end is an [`End`] whose `Inner` is `Rx` and whose `Error` is an `Rx::Error`. 33 | pub(super) type ReceiverEnd = End::Error, Rx, Tx, Rx>; 34 | 35 | impl Drop for End 36 | where 37 | Key: Eq + Hash, 38 | { 39 | fn drop(&mut self) { 40 | // Remove the entire entry at this key if the other end has already dropped 41 | let _ = self.managed.remove_if(&self.key, |_, waiting| { 42 | waiting.half_dropped.swap(true, Ordering::SeqCst) 43 | }); 44 | // Resize the backing map if it's now smaller than 25% capacity 45 | if self.managed.len().saturating_mul(4) < self.managed.capacity() { 46 | self.managed.shrink_to_fit(); 47 | } 48 | } 49 | } 50 | 51 | impl End 52 | where 53 | Key: Eq + Hash, 54 | { 55 | /// Create a new [`End`] from its inner pieces. 56 | pub(super) fn new( 57 | key: Key, 58 | inner: Inner, 59 | next: maybe_bounded::Receiver, 60 | recover: Arc Recovery + Sync + Send>, 61 | managed: Arc>, 62 | timeout: Option, 63 | ) -> Self { 64 | Self { 65 | key, 66 | inner: Some(inner), 67 | next, 68 | recover, 69 | managed, 70 | timeout, 71 | } 72 | } 73 | 74 | /// Given an error, interpret the contained recovery strategy and return `true` if and only if 75 | /// we should continue onwards. This function modifies `retries` and `deadline` to keep them 76 | /// updated, with the assumption that they are initialized on the first occurrence of this error 77 | /// at `0` and `None`, respectively. 78 | pub(super) async fn recover( 79 | &mut self, 80 | retries: &mut usize, 81 | deadline: &mut Option, 82 | error: &Err, 83 | ) -> bool { 84 | match (self.recover)(*retries, error) { 85 | Recovery::Fail => return false, 86 | Recovery::Reconnect => self.inner = None, 87 | } 88 | 89 | // Set the deadline if it hasn't already been set and this is the first attempt 90 | if *retries == 0 && deadline.is_none() { 91 | *deadline = self.timeout.map(|delay| Instant::now() + delay); 92 | } 93 | 94 | // Note that one more retry has occurred 95 | *retries += 1; 96 | 97 | // If we got here, we didn't time out 98 | true 99 | } 100 | 101 | /// Attempt to acquire a mutable reference to the `Inner` thing in this end (this will be either 102 | /// a `Tx` or an `Rx` in practice), waiting for a new connection to be accepted if necessary. 103 | /// 104 | /// If this operation last past the specified deadline, returns `None`. 105 | pub(super) async fn inner(&mut self, deadline: Option) -> Option<&mut Inner> { 106 | if self.inner.is_none() { 107 | self.inner = Some( 108 | timeout_at_option(deadline, self.next.recv()) 109 | .await 110 | .ok()? 111 | .expect("acceptor dropped before end that refers to it"), 112 | ); 113 | } 114 | 115 | // At this point, `self.inner` is always `Some` 116 | Some(self.inner.as_mut().unwrap()) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /dialectic-reconnect/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, time::Duration}; 2 | use tokio::time::{error::Elapsed, Instant}; 3 | 4 | /// Sleep for a given duration, or until a given deadline expires, returning `true` if the sleep 5 | /// completed before the deadline, or `false` if it did not. 6 | /// 7 | /// This short-circuits and immediately returns `false` if it would exceed the deadline. 8 | pub(crate) async fn sleep_until_or_deadline(duration: Duration, deadline: Option) -> bool { 9 | let wakeup = Instant::now() + duration; 10 | if let Some(deadline) = deadline { 11 | // If we would exceed the deadline, don't wait at all 12 | if deadline < wakeup { 13 | return false; 14 | } 15 | } 16 | tokio::time::sleep_until(wakeup).await; 17 | true 18 | } 19 | 20 | /// If the future completes by the deadline or the deadline is `None`, return its result; otherwise, 21 | /// return the [`Elapsed`] time. 22 | pub(crate) async fn timeout_at_option( 23 | deadline: Option, 24 | future: impl Future, 25 | ) -> Result { 26 | if let Some(deadline) = deadline { 27 | tokio::time::timeout_at(deadline, future).await 28 | } else { 29 | Ok(future.await) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dialectic-tokio-mpsc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dialectic-tokio-mpsc" 3 | version = "0.1.0" 4 | authors = ["Kenny Foner ", "Shea Leffler "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A backend for the Dialectic crate using Tokio's MPSC channels" 8 | repository = "https://github.com/boltlabs-inc/dialectic" 9 | homepage = "https://github.com/boltlabs-inc/dialectic" 10 | keywords = ["session", "types", "async", "channel", "protocol"] 11 | categories = ["asynchronous", "concurrency"] 12 | 13 | [dependencies] 14 | dialectic = { version = "0.4", path = "../dialectic" } 15 | thiserror = "1" 16 | tokio = { version = "1", features = ["sync"] } 17 | 18 | [package.metadata.docs.rs] 19 | all-features = true 20 | rustdoc-args = ["--cfg", "docsrs"] -------------------------------------------------------------------------------- /dialectic-tokio-mpsc/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /dialectic-tokio-mpsc/README.md: -------------------------------------------------------------------------------- 1 | [![Rust](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml/badge.svg)](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml) 2 | ![license: MIT](https://img.shields.io/github/license/boltlabs-inc/dialectic) 3 | [![crates.io](https://img.shields.io/crates/v/dialectic-tokio-mpsc)](https://crates.io/crates/dialectic-tokio-mpsc) 4 | [![docs.rs documentation](https://docs.rs/dialectic-tokio-mpsc/badge.svg)](https://docs.rs/dialectic-tokio-mpsc) 5 | 6 | This crate contains the Tokio/MPSC backend for [Dialectic](https://crates.io/crates/dialectic). It 7 | supports send/receive operations over all types which are `Send + Any`. This is useful for 8 | inter-task and inter-thread communication, especially if you're working on a protocol which needs to 9 | be tested locally and is written to be backend-agnostic. There are a few important types: 10 | 11 | - The `dialectic_tokio_mpsc::Chan

` synonym is a quick type synonym for a channel which uses a 12 | bounded MPSC `Sender`/`Receiver` pair, and analogously the 13 | `dialectic_tokio_mpsc::UnboundedChan

` provides a similar functionality for unbounded MPSC channels. 14 | - The `dialectic_tokio_mpsc::Receiver`/`Sender`/`UnboundedReceiver`/`UnboundedSender` types 15 | transparently wrap the underlying MPSC receiver/sender types. If not for the orphan rules, 16 | Dialectic's `Transmitter`/`Receiver`/`Transmit`/`Receive` *traits* would be directly implemented 17 | on the `tokio::mpsc` types, but Rust does not allow that. 18 | - Addditionally, `dialectic_tokio_mpsc::channel()` and `unbounded_channel()` are provided for 19 | conveniently constructing pairs of these transport types. -------------------------------------------------------------------------------- /dialectic-tokio-mpsc/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a backend implementation for the [`dialectic`] crate using 2 | //! [`tokio::sync::mpsc`] channels carrying boxed values `Box`, which are downcast to their true type (inferred from the session type) on the other 4 | //! end of the channel. Select this backend if you're using the Tokio runtime for asynchrony and 5 | //! you only need to communicate between tasks in the same process. 6 | 7 | #![allow(clippy::type_complexity)] 8 | #![warn(missing_docs)] 9 | #![warn(missing_copy_implementations, missing_debug_implementations)] 10 | #![warn(unused_qualifications, unused_results)] 11 | #![warn(future_incompatible)] 12 | #![warn(unused)] 13 | // Documentation configuration 14 | #![forbid(broken_intra_doc_links)] 15 | 16 | use dialectic::backend::{self, By, Choice, Mut, Ref, Transmittable, Val}; 17 | use std::{any::Any, future::Future, pin::Pin}; 18 | use thiserror::Error; 19 | use tokio::sync::mpsc; 20 | pub use tokio::sync::mpsc::error::SendError; 21 | 22 | /// Shorthand for a [`Chan`](dialectic::Chan) using a bounded [`Sender`] and [`Receiver`]. 23 | /// 24 | /// # Examples 25 | /// 26 | /// ``` 27 | /// use dialectic::prelude::*; 28 | /// use dialectic::types::Done; 29 | /// use dialectic_tokio_mpsc as mpsc; 30 | /// 31 | /// let _: (mpsc::Chan, mpsc::Chan) = 32 | /// Done::channel(|| mpsc::channel(1)); 33 | /// ``` 34 | pub type Chan

= dialectic::Chan; 35 | 36 | /// Shorthand for a [`Chan`](dialectic::Chan) using an unbounded [`UnboundedSender`] and 37 | /// [`UnboundedReceiver`]. 38 | /// 39 | /// # Examples 40 | /// 41 | /// ``` 42 | /// use dialectic::prelude::*; 43 | /// use dialectic::types::Done; 44 | /// use dialectic_tokio_mpsc as mpsc; 45 | /// 46 | /// let _: (mpsc::UnboundedChan, mpsc::UnboundedChan) = 47 | /// Done::channel(mpsc::unbounded_channel); 48 | /// ``` 49 | pub type UnboundedChan

= dialectic::Chan; 50 | 51 | /// A bounded receiver for dynamically typed values. See [`tokio::sync::mpsc::Receiver`]. 52 | #[derive(Debug)] 53 | pub struct Receiver(pub mpsc::Receiver>); 54 | 55 | /// A bounded sender for dynamically typed values. See [`tokio::sync::mpsc::Sender`]. 56 | #[derive(Debug, Clone)] 57 | pub struct Sender(pub mpsc::Sender>); 58 | 59 | /// An unbounded receiver for dynamically typed values. See 60 | /// [`tokio::sync::mpsc::UnboundedReceiver`]. 61 | #[derive(Debug)] 62 | pub struct UnboundedReceiver(pub mpsc::UnboundedReceiver>); 63 | 64 | /// An unbounded sender for dynamically typed values. See [`tokio::sync::mpsc::UnboundedSender`]. 65 | #[derive(Debug, Clone)] 66 | pub struct UnboundedSender(pub mpsc::UnboundedSender>); 67 | 68 | /// Create a bounded mpsc channel for transporting dynamically typed values. 69 | /// 70 | /// This is a wrapper around `tokio::sync::mpsc::channel::>`. See 71 | /// [`tokio::sync::mpsc::channel`]. 72 | /// 73 | /// # Examples 74 | /// 75 | /// ``` 76 | /// let (tx, rx) = dialectic_tokio_mpsc::channel(1); 77 | /// ``` 78 | pub fn channel(buffer: usize) -> (Sender, Receiver) { 79 | let (tx, rx) = mpsc::channel(buffer); 80 | (Sender(tx), Receiver(rx)) 81 | } 82 | 83 | /// Create an unbounded mpsc channel for transporting dynamically typed values. 84 | /// 85 | /// This is a wrapper around `tokio::sync::mpsc::channel::>`. See 86 | /// [`tokio::sync::mpsc::unbounded_channel`]. 87 | /// 88 | /// # Examples 89 | /// 90 | /// ``` 91 | /// let (tx, rx) = dialectic_tokio_mpsc::unbounded_channel(); 92 | /// ``` 93 | pub fn unbounded_channel() -> (UnboundedSender, UnboundedReceiver) { 94 | let (tx, rx) = mpsc::unbounded_channel(); 95 | (UnboundedSender(tx), UnboundedReceiver(rx)) 96 | } 97 | 98 | /// An error thrown while receiving from or sending to a dynamically typed [`tokio::sync::mpsc`] 99 | /// channel. 100 | #[derive(Debug)] 101 | pub enum Error { 102 | /// Error during receive. 103 | Recv(RecvError), 104 | /// Error during send. 105 | Send(Box), 106 | } 107 | 108 | impl std::fmt::Display for Error { 109 | fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 110 | match self { 111 | Error::Recv(e) => e.fmt(fmt), 112 | Error::Send(_) => write!(fmt, "Channel closed"), 113 | } 114 | } 115 | } 116 | 117 | impl std::error::Error for Error {} 118 | 119 | impl From for Error { 120 | fn from(err: RecvError) -> Self { 121 | Error::Recv(err) 122 | } 123 | } 124 | 125 | impl From> for Error { 126 | fn from(SendError(err): SendError) -> Self { 127 | Error::Send(Box::new(err)) 128 | } 129 | } 130 | 131 | /// An error thrown while receiving from a dynamically typed [`tokio::sync::mpsc`] channel. 132 | #[derive(Debug, Error)] 133 | pub enum RecvError { 134 | /// The channel was explicitly closed, or all senders were dropped, implicitly closing it. 135 | #[error("Channel closed")] 136 | Closed, 137 | /// A value received from the channel could not be cast into the correct expected type. This is 138 | /// always resultant from the other end of a channel failing to follow the session type of the 139 | /// channel. 140 | #[error("Received value of unexpected type")] 141 | DowncastFailed(Box), 142 | } 143 | 144 | impl backend::Transmitter for Sender { 145 | type Error = SendError>; 146 | 147 | fn send_choice<'async_lifetime, const LENGTH: usize>( 148 | &'async_lifetime mut self, 149 | choice: Choice, 150 | ) -> Pin> + Send + 'async_lifetime>> { 151 | >>::send(self, choice) 152 | } 153 | } 154 | 155 | impl backend::Transmit for Sender { 156 | fn send<'a, 'async_lifetime>( 157 | &'async_lifetime mut self, 158 | message: >::Type, 159 | ) -> Pin> + Send + 'async_lifetime>> 160 | where 161 | 'a: 'async_lifetime, 162 | { 163 | Box::pin(mpsc::Sender::send(&self.0, Box::new(message))) 164 | } 165 | } 166 | 167 | impl backend::Transmit for Sender 168 | where 169 | T: Transmittable + ToOwned + Send, 170 | T::Owned: Send + Any + 'static, 171 | { 172 | fn send<'a, 'async_lifetime>( 173 | &'async_lifetime mut self, 174 | message: >::Type, 175 | ) -> Pin> + Send + 'async_lifetime>> 176 | where 177 | 'a: 'async_lifetime, 178 | { 179 | Box::pin(mpsc::Sender::send(&self.0, Box::new(message.to_owned()))) 180 | } 181 | } 182 | 183 | impl backend::Transmit for Sender 184 | where 185 | T: Transmittable + ToOwned + Send, 186 | T::Owned: Send + Any + 'static, 187 | { 188 | fn send<'a, 'async_lifetime>( 189 | &'async_lifetime mut self, 190 | message: >::Type, 191 | ) -> Pin> + Send + 'async_lifetime>> 192 | where 193 | 'a: 'async_lifetime, 194 | { 195 | Box::pin(mpsc::Sender::send(&self.0, Box::new(message.to_owned()))) 196 | } 197 | } 198 | 199 | impl backend::Receiver for Receiver { 200 | type Error = RecvError; 201 | 202 | fn recv_choice<'async_lifetime, const LENGTH: usize>( 203 | &'async_lifetime mut self, 204 | ) -> Pin, Self::Error>> + Send + 'async_lifetime>> 205 | { 206 | >>::recv(self) 207 | } 208 | } 209 | 210 | impl backend::Receive for Receiver { 211 | fn recv<'async_lifetime>( 212 | &'async_lifetime mut self, 213 | ) -> Pin> + Send + 'async_lifetime>> { 214 | Box::pin(async move { 215 | match mpsc::Receiver::recv(&mut self.0).await { 216 | None => Err(RecvError::Closed), 217 | Some(b) => match b.downcast() { 218 | Err(b) => Err(RecvError::DowncastFailed(b)), 219 | Ok(t) => Ok(*t), 220 | }, 221 | } 222 | }) 223 | } 224 | } 225 | 226 | impl backend::Transmitter for UnboundedSender { 227 | type Error = SendError>; 228 | 229 | fn send_choice<'async_lifetime, const LENGTH: usize>( 230 | &'async_lifetime mut self, 231 | choice: Choice, 232 | ) -> Pin> + Send + 'async_lifetime>> { 233 | >>::send(self, choice) 234 | } 235 | } 236 | 237 | impl backend::Transmit for UnboundedSender { 238 | fn send<'a, 'async_lifetime>( 239 | &'async_lifetime mut self, 240 | message: >::Type, 241 | ) -> Pin> + Send + 'async_lifetime>> 242 | where 243 | 'a: 'async_lifetime, 244 | { 245 | Box::pin(async move { mpsc::UnboundedSender::send(&self.0, Box::new(message)) }) 246 | } 247 | } 248 | 249 | impl backend::Transmit for UnboundedSender 250 | where 251 | T: Transmittable + ToOwned + Send + Sync, 252 | T::Owned: Send + Any + 'static, 253 | { 254 | fn send<'a, 'async_lifetime>( 255 | &'async_lifetime mut self, 256 | message: >::Type, 257 | ) -> Pin> + Send + 'async_lifetime>> 258 | where 259 | 'a: 'async_lifetime, 260 | { 261 | Box::pin(async move { mpsc::UnboundedSender::send(&self.0, Box::new(message.to_owned())) }) 262 | } 263 | } 264 | 265 | impl backend::Transmit for UnboundedSender 266 | where 267 | T: Transmittable + ToOwned + Send, 268 | T::Owned: Send + Any + 'static, 269 | { 270 | fn send<'a, 'async_lifetime>( 271 | &'async_lifetime mut self, 272 | message: >::Type, 273 | ) -> Pin> + Send + 'async_lifetime>> 274 | where 275 | 'a: 'async_lifetime, 276 | { 277 | Box::pin(async move { mpsc::UnboundedSender::send(&self.0, Box::new(message.to_owned())) }) 278 | } 279 | } 280 | 281 | impl backend::Receiver for UnboundedReceiver { 282 | type Error = RecvError; 283 | 284 | fn recv_choice<'async_lifetime, const LENGTH: usize>( 285 | &'async_lifetime mut self, 286 | ) -> Pin, Self::Error>> + Send + 'async_lifetime>> 287 | { 288 | >>::recv(self) 289 | } 290 | } 291 | 292 | impl backend::Receive for UnboundedReceiver { 293 | fn recv<'async_lifetime>( 294 | &'async_lifetime mut self, 295 | ) -> Pin> + Send + 'async_lifetime>> { 296 | Box::pin(async move { 297 | match mpsc::UnboundedReceiver::recv(&mut self.0).await { 298 | None => Err(RecvError::Closed), 299 | Some(b) => match b.downcast() { 300 | Err(b) => Err(RecvError::DowncastFailed(b)), 301 | Ok(t) => Ok(*t), 302 | }, 303 | } 304 | }) 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /dialectic-tokio-serde-bincode/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dialectic-tokio-serde-bincode" 3 | version = "0.1.0" 4 | authors = ["Kenny Foner ", "Shea Leffler "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A backend for the Dialectic crate using Bincode serialization over Tokio transport" 8 | repository = "https://github.com/boltlabs-inc/dialectic" 9 | homepage = "https://github.com/boltlabs-inc/dialectic" 10 | keywords = ["session", "types", "async", "channel", "protocol"] 11 | categories = ["asynchronous", "concurrency", "encoding", "network-programming"] 12 | 13 | [dependencies] 14 | serde = { version = "1", features = ["derive"] } 15 | bincode = { version = "1.3" } 16 | bytes = { version = "1" } 17 | tokio = { version = "1", features = ["full"] } 18 | tokio-util = { version = "0.6", features = ["codec"] } 19 | dialectic = { version = "0.4", path = "../dialectic", optional = true } 20 | dialectic-tokio-serde = { version = "0.1", path = "../dialectic-tokio-serde" } 21 | 22 | [package.metadata.docs.rs] 23 | all-features = true 24 | rustdoc-args = ["--cfg", "docsrs"] -------------------------------------------------------------------------------- /dialectic-tokio-serde-bincode/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /dialectic-tokio-serde-bincode/README.md: -------------------------------------------------------------------------------- 1 | [![Rust](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml/badge.svg)](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml) 2 | ![license: MIT](https://img.shields.io/github/license/boltlabs-inc/dialectic) 3 | [![crates.io](https://img.shields.io/crates/v/dialectic-tokio-serde-bincode)](https://crates.io/crates/dialectic-tokio-serde-bincode) 4 | [![docs.rs documentation](https://docs.rs/dialectic-tokio-serde-bincode/badge.svg)](https://docs.rs/dialectic-tokio-serde-bincode) 5 | 6 | A Tokio-compatible [Dialectic](https://crates.io/crates/dialectic) backend plugin for the 7 | [`dialectic-tokio-serde`](https://crates.io/crates/dialectic-tokio-serde) crate. It can be used with 8 | any transport that supports Tokio's `AsyncWrite`/`AsyncRead` traits, and can send/receive any types 9 | which implement serde's `Serialize`/`Deserialize` traits. This backend serializes its data using the 10 | [`bincode` format.](https://crates.io/crates/bincode) -------------------------------------------------------------------------------- /dialectic-tokio-serde-bincode/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides an implementation of the [`bincode`] serialization format compatible with 2 | //! the [`dialectic-tokio-serde`] backend for the [`dialectic`] crate. To use it, you will also need 3 | //! to import the [`dialectic-tokio-serde`] crate. 4 | //! 5 | //! [`dialectic`]: https://docs.rs/dialectic 6 | #![warn(missing_docs)] 7 | #![warn(missing_copy_implementations, missing_debug_implementations)] 8 | #![warn(unused_qualifications, unused_results)] 9 | #![warn(future_incompatible)] 10 | #![warn(unused)] 11 | // Documentation configuration 12 | #![forbid(broken_intra_doc_links)] 13 | 14 | use bytes::Bytes; 15 | use dialectic_tokio_serde::*; 16 | use serde::{Deserialize, Serialize}; 17 | use tokio::io::{AsyncRead, AsyncWrite}; 18 | use tokio_util::codec::length_delimited::LengthDelimitedCodec; 19 | 20 | /// The [Bincode](bincode) binary serialization format. 21 | /// 22 | /// To construct with default options, use `Bincode::default()`. To configure custom options, use 23 | /// the Bincode crate's [bincode::Options] builder, then the [`.into()`](Into::into) method to 24 | /// construct a [`Bincode`]. 25 | #[derive(Debug, Clone, Copy, Default)] 26 | pub struct Bincode(O); 27 | 28 | impl From for Bincode { 29 | fn from(o: O) -> Self { 30 | Bincode(o) 31 | } 32 | } 33 | 34 | impl Serializer for Bincode { 35 | type Error = bincode::Error; 36 | type Output = Bytes; 37 | 38 | fn serialize(&mut self, item: &T) -> Result { 39 | self.0.clone().serialize(item).map(Into::into) 40 | } 41 | } 42 | 43 | impl Deserializer for Bincode 44 | where 45 | Input: AsRef<[u8]>, 46 | { 47 | type Error = bincode::Error; 48 | 49 | fn deserialize Deserialize<'a>>(&mut self, src: &Input) -> Result { 50 | self.0.clone().deserialize(src.as_ref()) 51 | } 52 | } 53 | 54 | /// Pairs the [`Bincode`] serialization format with the 55 | /// [`LengthDelimitedCodec`](tokio_util::codec::LengthDelimitedCodec) for framing, returning a 56 | /// symmetrical pair of [`Sender`] and [`Receiver`]. 57 | /// 58 | /// The `length_field_bytes` parameter indicates how many bytes to use for representing the length 59 | /// of each frame on the wire, and must range between 1 and 8, inclusive. 60 | /// 61 | /// The `max_length` parameter indicates the maximum length of any message received or sent. An 62 | /// error is thrown during encoding and decoding if a message would exceed this length. 63 | pub fn length_delimited( 64 | writer: W, 65 | reader: R, 66 | length_field_bytes: usize, 67 | max_length: usize, 68 | ) -> ( 69 | Sender, 70 | Receiver, 71 | ) { 72 | let make_codec = || { 73 | LengthDelimitedCodec::builder() 74 | .max_frame_length(max_length) 75 | .length_field_length(length_field_bytes) 76 | .new_codec() 77 | }; 78 | ( 79 | Sender::new(Bincode::default(), make_codec(), writer), 80 | Receiver::new(Bincode::default(), make_codec(), reader), 81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /dialectic-tokio-serde-json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dialectic-tokio-serde-json" 3 | version = "0.1.0" 4 | authors = ["Kenny Foner ", "Shea Leffler "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A backend for the Dialectic crate using JSON serialization over Tokio transport" 8 | repository = "https://github.com/boltlabs-inc/dialectic" 9 | homepage = "https://github.com/boltlabs-inc/dialectic" 10 | categories = ["asynchronous", "concurrency", "encoding", "network-programming"] 11 | 12 | [dependencies] 13 | serde = { version = "1", features = ["derive"] } 14 | serde_json = { version = "1" } 15 | tokio = { version = "1", features = ["full"] } 16 | tokio-util = { version = "0.6", features = ["codec"] } 17 | dialectic = { version = "0.4", path = "../dialectic", optional = true } 18 | dialectic-tokio-serde = { version = "0.1", path = "../dialectic-tokio-serde" } 19 | 20 | [package.metadata.docs.rs] 21 | all-features = true 22 | rustdoc-args = ["--cfg", "docsrs"] -------------------------------------------------------------------------------- /dialectic-tokio-serde-json/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /dialectic-tokio-serde-json/README.md: -------------------------------------------------------------------------------- 1 | [![Rust](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml/badge.svg)](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml) 2 | ![license: MIT](https://img.shields.io/github/license/boltlabs-inc/dialectic) 3 | [![crates.io](https://img.shields.io/crates/v/dialectic-tokio-serde-json)](https://crates.io/crates/dialectic-tokio-serde-json) 4 | [![docs.rs documentation](https://docs.rs/dialectic-tokio-serde-json/badge.svg)](https://docs.rs/dialectic-tokio-serde-json) 5 | 6 | A Tokio-compatible [Dialectic](https://crates.io/crates/dialectic) backend plugin for the 7 | [`dialectic-tokio-serde`](https://crates.io/crates/dialectic-tokio-serde) crate. It can be used with 8 | any transport that supports Tokio's `AsyncWrite`/`AsyncRead` traits, and can send/receive any types 9 | which implement serde's `Serialize`/`Deserialize` traits. This backend serializes its data using 10 | the [`serde_json` crate.](https://crates.io/crates/serde_json) -------------------------------------------------------------------------------- /dialectic-tokio-serde-json/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides an implementation of the [`json`](serde_json) serialization format 2 | //! compatible with the [`dialectic-tokio-serde`] backend for the [`dialectic`] crate. To use it, 3 | //! you will also need to import the [`dialectic-tokio-serde`] crate. 4 | //! 5 | //! [`dialectic`]: https://docs.rs/dialectic 6 | #![warn(missing_docs)] 7 | #![warn(missing_copy_implementations, missing_debug_implementations)] 8 | #![warn(unused_qualifications, unused_results)] 9 | #![warn(future_incompatible)] 10 | #![warn(unused)] 11 | // Documentation configuration 12 | #![forbid(broken_intra_doc_links)] 13 | 14 | use dialectic_tokio_serde::*; 15 | use serde::{Deserialize, Serialize}; 16 | use serde_json as json; 17 | use tokio::io::{AsyncRead, AsyncWrite}; 18 | use tokio_util::codec::LinesCodec; 19 | 20 | /// The [JSON](json) serialization format. 21 | #[derive(Debug, Clone, Copy)] 22 | pub struct Json { 23 | pretty: bool, 24 | } 25 | 26 | impl Json { 27 | /// Construct a new `Json` serialization format. 28 | pub fn new() -> Self { 29 | Json::default() 30 | } 31 | 32 | /// Construct a new `Json` serialization format which pretty-formats its output. 33 | /// 34 | /// ⚠️ **Caution:** Pretty-printed JSON serialization is **not compatible with line-delimited 35 | /// codecs** such as [`LinesCodec`], because pretty-printed JSON contains newlines. Combining 36 | /// the two will result in runtime deserialization failures. 37 | pub fn pretty() -> Self { 38 | Json { pretty: true } 39 | } 40 | } 41 | 42 | impl Default for Json { 43 | fn default() -> Self { 44 | Json { pretty: false } 45 | } 46 | } 47 | 48 | impl Serializer for Json { 49 | type Error = json::Error; 50 | type Output = String; 51 | 52 | fn serialize(&mut self, item: &T) -> Result { 53 | json::to_string(item) 54 | } 55 | } 56 | 57 | impl> Deserializer for Json { 58 | type Error = json::Error; 59 | 60 | fn deserialize Deserialize<'a>>(&mut self, src: &Input) -> Result { 61 | json::from_str(src.as_ref()) 62 | } 63 | } 64 | 65 | /// Pairs the [`Json`] serialization format with the [`LinesCodec`](tokio_util::codec::LinesCodec) 66 | /// for framing, returning a symmetrical pair of [`Sender`] and [`Receiver`]. 67 | /// 68 | /// The `max_length` parameter indicates the maximum length of any message received or sent. An 69 | /// error is thrown during encoding and decoding if a message exceeds this length. 70 | pub fn lines( 71 | writer: W, 72 | reader: R, 73 | max_length: usize, 74 | ) -> (Sender, Receiver) { 75 | symmetrical( 76 | Json::default(), 77 | LinesCodec::new_with_max_length(max_length), 78 | writer, 79 | reader, 80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /dialectic-tokio-serde/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dialectic-tokio-serde" 3 | version = "0.1.0" 4 | authors = ["Kenny Foner ", "Shea Leffler "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A backend builder for the Dialectic crate using Serde serialization over Tokio transport" 8 | repository = "https://github.com/boltlabs-inc/dialectic" 9 | homepage = "https://github.com/boltlabs-inc/dialectic" 10 | keywords = ["session", "types", "async", "channel", "protocol"] 11 | categories = ["asynchronous", "concurrency", "encoding", "network-programming"] 12 | 13 | [dependencies] 14 | dialectic = { version = "0.4", path = "../dialectic", features = ["serde"] } 15 | futures = { version = "0.3", default-features = false } 16 | serde = { version = "1" } 17 | thiserror = "1" 18 | tokio = { version = "1", features = ["full"] } 19 | tokio-util = { version = "0.6", features = ["codec"] } 20 | 21 | [package.metadata.docs.rs] 22 | all-features = true 23 | rustdoc-args = ["--cfg", "docsrs"] -------------------------------------------------------------------------------- /dialectic-tokio-serde/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /dialectic-tokio-serde/README.md: -------------------------------------------------------------------------------- 1 | [![Rust](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml/badge.svg)](https://github.com/boltlabs-inc/dialectic/actions/workflows/rust.yml) 2 | ![license: MIT](https://img.shields.io/github/license/boltlabs-inc/dialectic) 3 | [![crates.io](https://img.shields.io/crates/v/dialectic-tokio-serde)](https://crates.io/crates/dialectic-tokio-serde) 4 | [![docs.rs documentation](https://docs.rs/dialectic-tokio-serde/badge.svg)](https://docs.rs/dialectic-tokio-serde) 5 | 6 | The `dialectic-tokio-serde` crate provides [Dialectic](https://crates.io/crates/dialectic) 7 | `Transmitter` and `Receiver` types which are capable of sending/receiving any 8 | serde-`Serialize`/`Deserialize` type. These are generic over their transport, and require three 9 | additional parameters to function: 10 | 11 | - Async-capable writer/reader types which implement the Tokio `AsyncWrite` and `AsyncRead` traits. 12 | - A Tokio codec, for encoding and decoding frames which are written to/read from the asynchronous 13 | writer/reader. 14 | - A `dialectic_tokio_serde::Serializer`/`Deserializer` for converting the sent/received types 15 | to/from the wire format. *PLEASE NOTE! These are not the serde `Serializer`/`Deserializer` traits 16 | but rather similar traits which also define the possible output/input types of the 17 | `Serializer`/`Deserializer.`* 18 | 19 | ## Tokio/serde backends for Dialectic 20 | 21 | Currently, two formats are implemented as sister crates: 22 | 23 | - The [`dialectic-tokio-serde-bincode`](https://crates.io/crates/dialectic-tokio-serde-bincode) 24 | crate, which when provided an `AsyncWrite`/`AsyncRead` transport enables serialization to/from the 25 | bincode format and using the `tokio_util` `LengthDelimitedCodec` for a wire encoding. 26 | - The [`dialectic-tokio-serde-json`](https://crates.io/crates/dialectic-tokio-serde-json) crate, 27 | which when provided an `AsyncWrite`/`AsyncRead` transport enables serialization to/from the JSON 28 | format and using the `tokio_util` `LinesCodec` for a wire encoding. -------------------------------------------------------------------------------- /dialectic-tokio-serde/src/error.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// Shorthand for an [`Error`] resulting from a symmetrically serialized/encoded connection. 4 | pub type SymmetricalError = Error; 5 | 6 | /// An error during operations on a [`Sender`] or [`Receiver`], unifying [`SendError`] and 7 | /// [`RecvError`]. 8 | pub enum Error, E: Encoder, D: Decoder> { 9 | /// An error occurred while attempting to [`send`](Transmit::send) a value. 10 | Send(SendError), 11 | /// An error occurred while attempting to [`recv`](Receive::recv) a value. 12 | Recv(RecvError), 13 | } 14 | 15 | /// An error while sending on a [`Sender`]. 16 | pub enum SendError> { 17 | /// An error occurred while attempting to serialize a value. 18 | Serialize(F::Error), 19 | /// An error occurred while attempting to encode and transmit a serialized value as a frame. 20 | Encode(E::Error), 21 | } 22 | 23 | /// An error while receiving from a [`Receiver`]. 24 | pub enum RecvError, D: Decoder> { 25 | /// An error occurred while attempting to deserialize a value. 26 | Deserialize(F::Error), 27 | /// An error occurred while attempting to receive and decode a serialized value as a frame. 28 | Decode(D::Error), 29 | /// The underlying stream was closed. 30 | Closed, 31 | } 32 | 33 | impl From> for Error 34 | where 35 | F: Serializer, 36 | G: Deserializer, 37 | E: Encoder, 38 | D: Decoder, 39 | { 40 | fn from(err: SendError) -> Self { 41 | Error::Send(err) 42 | } 43 | } 44 | 45 | impl From> for Error 46 | where 47 | F: Serializer, 48 | G: Deserializer, 49 | E: Encoder, 50 | D: Decoder, 51 | { 52 | fn from(err: RecvError) -> Self { 53 | Error::Recv(err) 54 | } 55 | } 56 | 57 | impl std::error::Error for Error 58 | where 59 | F: Serializer, 60 | G: Deserializer, 61 | E: Encoder, 62 | D: Decoder, 63 | F::Error: Debug + Display, 64 | G::Error: Debug + Display, 65 | E::Error: Debug + Display, 66 | D::Error: Debug + Display, 67 | { 68 | } 69 | 70 | impl, E: Encoder, D: Decoder> Debug 71 | for Error 72 | where 73 | F::Error: Debug, 74 | G::Error: Debug, 75 | E::Error: Debug, 76 | D::Error: Debug, 77 | { 78 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 79 | match self { 80 | Error::Send(err) => write!(f, "{:?}", err), 81 | Error::Recv(err) => write!(f, "{:?}", err), 82 | } 83 | } 84 | } 85 | 86 | impl, E: Encoder, D: Decoder> Display 87 | for Error 88 | where 89 | F::Error: Display, 90 | G::Error: Display, 91 | E::Error: Display, 92 | D::Error: Display, 93 | { 94 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 95 | match self { 96 | Error::Send(err) => write!(f, "{}", err), 97 | Error::Recv(err) => write!(f, "{}", err), 98 | } 99 | } 100 | } 101 | 102 | impl> Debug for SendError 103 | where 104 | F::Error: Debug, 105 | E::Error: Debug, 106 | { 107 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 108 | match self { 109 | SendError::Serialize(err) => write!(f, "{:?}", err), 110 | SendError::Encode(err) => write!(f, "{:?}", err), 111 | } 112 | } 113 | } 114 | 115 | impl> Display for SendError 116 | where 117 | F::Error: Display, 118 | E::Error: Display, 119 | { 120 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 121 | match self { 122 | SendError::Serialize(err) => write!(f, "{}", err), 123 | SendError::Encode(err) => write!(f, "{}", err), 124 | } 125 | } 126 | } 127 | 128 | impl std::error::Error for SendError 129 | where 130 | F: Serializer, 131 | E: Encoder, 132 | F::Error: Display + Debug, 133 | E::Error: Display + Debug, 134 | { 135 | } 136 | 137 | impl, D: Decoder> Debug for RecvError 138 | where 139 | F::Error: Debug, 140 | D::Error: Debug, 141 | { 142 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 143 | match self { 144 | RecvError::Deserialize(err) => write!(f, "{:?}", err), 145 | RecvError::Decode(err) => write!(f, "{:?}", err), 146 | RecvError::Closed => write!(f, "Closed"), 147 | } 148 | } 149 | } 150 | 151 | impl, D: Decoder> Display for RecvError 152 | where 153 | F::Error: Display, 154 | D::Error: Display, 155 | { 156 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 157 | match self { 158 | RecvError::Deserialize(err) => write!(f, "{}", err), 159 | RecvError::Decode(err) => write!(f, "{}", err), 160 | RecvError::Closed => write!(f, "Connection closed"), 161 | } 162 | } 163 | } 164 | 165 | impl std::error::Error for RecvError 166 | where 167 | F: Deserializer, 168 | D: Decoder, 169 | F::Error: Display + Debug, 170 | D::Error: Display + Debug, 171 | { 172 | } 173 | 174 | impl Clone for Error 175 | where 176 | F: Serializer, 177 | G: Deserializer, 178 | E: Encoder, 179 | D: Decoder, 180 | F::Error: Clone, 181 | G::Error: Clone, 182 | E::Error: Clone, 183 | D::Error: Clone, 184 | { 185 | fn clone(&self) -> Self { 186 | match self { 187 | Error::Send(err) => Error::Send(err.clone()), 188 | Error::Recv(err) => Error::Recv(err.clone()), 189 | } 190 | } 191 | } 192 | 193 | impl Clone for SendError 194 | where 195 | F: Serializer, 196 | E: Encoder, 197 | F::Error: Clone, 198 | E::Error: Clone, 199 | { 200 | fn clone(&self) -> Self { 201 | match self { 202 | SendError::Serialize(err) => SendError::Serialize(err.clone()), 203 | SendError::Encode(err) => SendError::Encode(err.clone()), 204 | } 205 | } 206 | } 207 | 208 | impl Clone for RecvError 209 | where 210 | F: Deserializer, 211 | D: Decoder, 212 | F::Error: Clone, 213 | D::Error: Clone, 214 | { 215 | fn clone(&self) -> Self { 216 | match self { 217 | RecvError::Deserialize(err) => RecvError::Deserialize(err.clone()), 218 | RecvError::Decode(err) => RecvError::Decode(err.clone()), 219 | RecvError::Closed => RecvError::Closed, 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /dialectic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dialectic" 3 | version = "0.4.1" 4 | authors = ["Kenny Foner ", "Shea Leffler "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Transport-polymorphic, asynchronous session types for Rust" 8 | repository = "https://github.com/boltlabs-inc/dialectic" 9 | homepage = "https://github.com/boltlabs-inc/dialectic" 10 | keywords = ["session", "types", "async", "channel", "protocol"] 11 | categories = ["asynchronous", "concurrency", "network-programming"] 12 | readme = "../README.md" 13 | 14 | [dependencies] 15 | thiserror = "1" 16 | call-by = { version = "^0.2.2" } 17 | tokio = { version = "1", optional = true } 18 | tokio-util = { version = "0.6", features = ["codec"], optional = true } 19 | serde = { version = "1", features = ["derive"], optional = true } 20 | bincode = { version = "1.3", optional = true } 21 | serde_json = { version = "1", optional = true } 22 | futures = { version = "0.3", default-features = false } 23 | derivative = "2.1" 24 | pin-project = "1" 25 | static_assertions = "1.1" 26 | dialectic-macro = { version = "0.1", path = "../dialectic-macro" } 27 | 28 | [dev-dependencies] 29 | tokio = { version = "1", features = ["full"] } 30 | static_assertions = "1.1" 31 | serde = "1" 32 | futures = "0.3" 33 | colored = "2" 34 | structopt = "0.3" 35 | criterion = { version = "0.3", features = ["async_tokio"] } 36 | dialectic-null = { version = "0.1", path = "../dialectic-null" } 37 | dialectic-tokio-mpsc = { version = "0.1", path = "../dialectic-tokio-mpsc" } 38 | dialectic-tokio-serde = { version = "0.1", path = "../dialectic-tokio-serde" } 39 | dialectic-tokio-serde-bincode = { version = "0.1", path = "../dialectic-tokio-serde-bincode" } 40 | 41 | [package.metadata.docs.rs] 42 | all-features = true 43 | rustdoc-args = ["--cfg", "docsrs"] 44 | 45 | [[bench]] 46 | name = "mpsc" 47 | harness = false 48 | 49 | [[bench]] 50 | name = "micro" 51 | harness = false 52 | 53 | [[example]] 54 | name = "common" 55 | crate-type = ["staticlib"] 56 | 57 | [[example]] 58 | name = "hello" 59 | 60 | [[example]] 61 | name = "stack" 62 | 63 | [[example]] 64 | name = "tally" 65 | 66 | [[example]] 67 | name = "template" 68 | -------------------------------------------------------------------------------- /dialectic/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /dialectic/benches/micro.rs: -------------------------------------------------------------------------------- 1 | use criterion::{ 2 | async_executor::AsyncExecutor, criterion_group, criterion_main, measurement::WallTime, Bencher, 3 | BenchmarkGroup, Criterion, 4 | }; 5 | use dialectic::prelude::*; 6 | use dialectic_null as null; 7 | use dialectic_tokio_mpsc as mpsc; 8 | use futures::Future; 9 | use std::{convert::TryInto, fmt::Debug, marker, sync::Arc, time::Instant}; 10 | use tokio::runtime::Runtime; 11 | 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 13 | enum Primitive { 14 | Send, 15 | Recv, 16 | Choose, 17 | Offer, 18 | Call, 19 | Split, 20 | } 21 | 22 | #[Transmitter(Tx for ())] 23 | async fn send( 24 | chan: Chan, 25 | ) -> Chan 26 | where 27 | Rx: Send, 28 | Tx::Error: Debug, 29 | { 30 | chan.send(()).await.unwrap() 31 | } 32 | 33 | #[Receiver(Rx for ())] 34 | async fn recv( 35 | chan: Chan, 36 | ) -> Chan 37 | where 38 | Tx: Send, 39 | Rx::Error: Debug, 40 | { 41 | chan.recv().await.unwrap().1 42 | } 43 | 44 | #[Transmitter(Tx)] 45 | async fn choose( 46 | chan: Chan {} } } }, Tx, Rx>, 47 | ) -> Chan {} } } }, Tx, Rx> 48 | where 49 | Rx: Send, 50 | Tx::Error: Debug, 51 | { 52 | chan.choose::<0>().await.unwrap() 53 | } 54 | 55 | #[Receiver(Rx)] 56 | async fn offer( 57 | chan: Chan {} } } }, Tx, Rx>, 58 | ) -> Chan {} } } }, Tx, Rx> 59 | where 60 | Tx: Send, 61 | Rx::Error: Debug, 62 | { 63 | offer!(in chan { 64 | 0 => chan, 65 | }) 66 | .unwrap() 67 | } 68 | 69 | async fn call( 70 | chan: Chan, 71 | ) -> Chan 72 | where 73 | Tx: Send, 74 | Rx: Send, 75 | { 76 | chan.call(|_| async { Ok::<_, ()>(()) }) 77 | .await 78 | .unwrap() 79 | .1 80 | .unwrap() 81 | } 82 | 83 | async fn split( 84 | chan: Chan {}, <- {} } } }, Tx, Rx>, 85 | ) -> Chan {} <- {} } } }, Tx, Rx> 86 | where 87 | Tx: Send, 88 | Rx: Send, 89 | { 90 | chan.split(|_, _| async { Ok::<_, ()>(()) }) 91 | .await 92 | .unwrap() 93 | .1 94 | .unwrap() 95 | } 96 | 97 | fn bench_chan_loop( 98 | b: &mut Bencher, 99 | rt: Arc, 100 | channel: N, 101 | primitive: Primitive, 102 | f: F, 103 | ) where 104 | F: Fn(Chan) -> Fut, 105 | Fut: Future>, 106 | N: Fn(Primitive, u64) -> (Tx, Rx, H), 107 | S: Session, 108 | Tx: marker::Send + 'static, 109 | Rx: marker::Send + 'static, 110 | A: AsyncExecutor, 111 | { 112 | b.iter_custom(|iters| { 113 | let (tx, rx, drop_after_bench) = channel(primitive, iters); 114 | let mut chan = S::wrap(tx, rx); 115 | let elapsed = rt.block_on(async { 116 | let start = Instant::now(); 117 | for _ in 0..iters { 118 | chan = f(chan).await; 119 | } 120 | start.elapsed() 121 | }); 122 | drop(drop_after_bench); 123 | elapsed 124 | }); 125 | } 126 | 127 | fn bench_chan_loop_group( 128 | g: &mut BenchmarkGroup, 129 | rt: Arc, 130 | channel: fn(Primitive, u64) -> (Tx, Rx, H), 131 | name: &str, 132 | primitive: Primitive, 133 | f: fn(Chan) -> Fut, 134 | ) where 135 | Fut: Future>, 136 | S: Session, 137 | Tx: marker::Send + 'static, 138 | Rx: marker::Send + 'static, 139 | A: AsyncExecutor, 140 | { 141 | g.bench_function(name, move |b| { 142 | bench_chan_loop(b, rt.clone(), channel, primitive, f) 143 | }); 144 | } 145 | 146 | #[Transmitter(Tx for ())] 147 | #[Receiver(Rx for ())] 148 | fn bench_all_on( 149 | c: &mut Criterion, 150 | rt_name: &str, 151 | rt: Arc, 152 | backend_name: &str, 153 | channel: fn(Primitive, u64) -> (Tx, Rx, H), 154 | ) where 155 | Tx::Error: Debug, 156 | Rx::Error: Debug, 157 | A: AsyncExecutor, 158 | { 159 | use Primitive::*; 160 | let group_name = format!("{}/{}", rt_name, backend_name); 161 | let mut g = c.benchmark_group(&group_name); 162 | bench_chan_loop_group(&mut g, rt.clone(), channel, "send", Send, send); 163 | bench_chan_loop_group(&mut g, rt.clone(), channel, "recv", Recv, recv); 164 | bench_chan_loop_group(&mut g, rt.clone(), channel, "choose", Choose, choose); 165 | bench_chan_loop_group(&mut g, rt.clone(), channel, "offer", Offer, offer); 166 | bench_chan_loop_group(&mut g, rt.clone(), channel, "call", Call, call); 167 | bench_chan_loop_group(&mut g, rt, channel, "split", Split, split); 168 | g.finish(); 169 | } 170 | 171 | fn bench_tokio_null(c: &mut Criterion) { 172 | bench_all_on( 173 | c, 174 | "tokio", 175 | Arc::new(Runtime::new().unwrap()), 176 | "null", 177 | |_primitive, _iters| (null::Sender::default(), null::Receiver::default(), ()), 178 | ) 179 | } 180 | 181 | fn bench_tokio_mpsc(c: &mut Criterion) { 182 | use Primitive::*; 183 | bench_all_on( 184 | c, 185 | "tokio", 186 | Arc::new(Runtime::new().unwrap()), 187 | "mpsc", 188 | |primitive, iters| { 189 | let (tx0, rx0) = mpsc::unbounded_channel(); 190 | let (tx1, rx1) = mpsc::unbounded_channel(); 191 | // Pre-allocate responses for those operations which need responses 192 | match primitive { 193 | Send | Choose | Call | Split => {} 194 | Recv => { 195 | for _ in 0..iters { 196 | tx1.0.send(Box::new(())).unwrap(); 197 | } 198 | } 199 | Offer => { 200 | for _ in 0..iters { 201 | let zero_choice: Choice<1> = 0u8.try_into().unwrap(); 202 | tx1.0.send(Box::new(zero_choice)).unwrap(); 203 | } 204 | } 205 | }; 206 | (tx0, rx1, (tx1, rx0)) 207 | }, 208 | ) 209 | } 210 | 211 | criterion_group!(benches, bench_tokio_null, bench_tokio_mpsc); 212 | criterion_main!(benches); 213 | -------------------------------------------------------------------------------- /dialectic/benches/mpsc.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; 2 | use dialectic::prelude::*; 3 | use dialectic::Unavailable; 4 | use dialectic_tokio_mpsc as mpsc; 5 | use std::time::Instant; 6 | use std::{sync::Arc, time::Duration}; 7 | use tokio::runtime::Runtime; 8 | 9 | type Server = Session! { loop { send bool } }; 10 | type Client = ::Dual; 11 | 12 | async fn dialectic_client(mut chan: Chan) { 13 | while { 14 | let (b, c) = chan.recv().await.unwrap(); 15 | chan = c; 16 | b 17 | } {} 18 | } 19 | 20 | async fn plain_client(mut rx: mpsc::Receiver) { 21 | while *rx.0.recv().await.unwrap().downcast().unwrap() {} 22 | } 23 | 24 | async fn dialectic_server(iterations: usize, mut chan: Chan) { 25 | for _ in 0..iterations { 26 | chan = chan.send(true).await.unwrap(); 27 | } 28 | let _ = chan.send(false).await.unwrap(); 29 | } 30 | 31 | async fn plain_server(iterations: usize, tx: mpsc::Sender) { 32 | for _ in 0..iterations { 33 | tx.0.send(Box::new(true)).await.unwrap(); 34 | } 35 | tx.0.send(Box::new(false)).await.unwrap(); 36 | } 37 | 38 | fn bench_send_recv(c: &mut Criterion) { 39 | let size: usize = 1024; 40 | 41 | let mut dialectic_group = c.benchmark_group("dialectic"); 42 | dialectic_group.throughput(Throughput::Elements(size as u64)); 43 | 44 | let rt = Arc::new(Runtime::new().unwrap()); 45 | 46 | dialectic_group.bench_with_input(BenchmarkId::new("recv", size), &size, |b, &s| { 47 | b.iter_custom(|iters| { 48 | let mut total_duration = Duration::from_secs(0); 49 | for _ in 0..iters { 50 | // Pre-populate the channel with things to receive 51 | let (tx, rx) = mpsc::channel(s + 1); 52 | rt.block_on(plain_server(s, tx.clone())); 53 | let client = Client::wrap(Unavailable::default(), rx); 54 | let start = Instant::now(); 55 | rt.block_on(dialectic_client(client)); 56 | total_duration += start.elapsed(); 57 | drop(tx); 58 | } 59 | total_duration 60 | }); 61 | }); 62 | 63 | dialectic_group.bench_with_input(BenchmarkId::new("send", size), &size, |b, &s| { 64 | b.iter_custom(|iters| { 65 | let mut total_duration = Duration::from_secs(0); 66 | for _ in 0..iters { 67 | let (tx, rx) = mpsc::channel(s + 1); 68 | let server = Server::wrap(tx, Unavailable::default()); 69 | let start = Instant::now(); 70 | rt.block_on(dialectic_server(s, server)); 71 | total_duration += start.elapsed(); 72 | drop(rx); 73 | } 74 | total_duration 75 | }); 76 | }); 77 | 78 | drop(dialectic_group); 79 | 80 | let mut plain_group = c.benchmark_group("plain"); 81 | plain_group.throughput(Throughput::Elements(size as u64)); 82 | 83 | plain_group.bench_with_input(BenchmarkId::new("recv", size), &size, |b, &s| { 84 | b.iter_custom(|iters| { 85 | let mut total_duration = Duration::from_secs(0); 86 | for _ in 0..iters { 87 | // Pre-populate the channel with things to receive 88 | let (tx, rx) = mpsc::channel(s + 1); 89 | rt.block_on(plain_server(s, tx.clone())); 90 | let start = Instant::now(); 91 | rt.block_on(plain_client(rx)); 92 | total_duration += start.elapsed(); 93 | drop(tx); 94 | } 95 | total_duration 96 | }); 97 | }); 98 | 99 | plain_group.bench_with_input(BenchmarkId::new("send", size), &size, |b, &s| { 100 | b.iter_custom(|iters| { 101 | let mut total_duration = Duration::from_secs(0); 102 | for _ in 0..iters { 103 | let (tx, rx) = mpsc::channel(s + 1); 104 | let start = Instant::now(); 105 | rt.block_on(plain_server(s, tx)); 106 | total_duration += start.elapsed(); 107 | drop(rx); 108 | } 109 | total_duration 110 | }); 111 | }); 112 | } 113 | 114 | criterion_group!(benches, bench_send_recv); 115 | criterion_main!(benches); 116 | -------------------------------------------------------------------------------- /dialectic/build.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | use std::fs::File; 3 | use std::io::Write; 4 | use std::path::Path; 5 | use std::{env, error::Error}; 6 | 7 | fn main() -> Result<(), Box> { 8 | // We auto-generate unit tests for session types enumerated within certain size bounds. 9 | // This makes it much less likely that an error in trait definition will be un-caught. 10 | 11 | // Open a file to write to it in the output directory for the build 12 | let out_dir = env::var("OUT_DIR").unwrap(); 13 | let dest_path = Path::new(&out_dir).join("valid_sessions.rs"); 14 | let mut f = File::create(&dest_path).unwrap(); 15 | 16 | // Define the sessions we wish to test 17 | let deep = Session::enumerate(6, 1, 1); 18 | let medium = Session::enumerate(3, 2, 3); 19 | let wide = Session::enumerate(2, 4, 127); 20 | let sessions = deep.chain(medium.chain(wide)); 21 | 22 | // File header 23 | writeln!(f, "#[deny(unused_imports)]")?; // test failure if we don't cover every constructor 24 | writeln!( 25 | f, 26 | "use crate::types::{{Send, Recv, Choose, Offer, Call, Split, Loop, Continue, Done}};" 27 | )?; 28 | writeln!(f, "use crate::Session;")?; 29 | writeln!(f, "use static_assertions::assert_impl_all;")?; 30 | writeln!(f)?; 31 | 32 | // Write out the test 33 | writeln!(f, "#[test]")?; 34 | writeln!(f, "fn all_sessions_valid() {{")?; 35 | for s in sessions { 36 | writeln!(f, " assert_impl_all!({}: Session);", s)?; 37 | } 38 | writeln!(f, "}}")?; 39 | Ok(()) 40 | } 41 | 42 | #[derive(Clone, Debug)] 43 | pub enum Session { 44 | Done, 45 | Recv(Box), 46 | Send(Box), 47 | Choose(Vec), 48 | Offer(Vec), 49 | Loop(Box), 50 | Continue(u8), 51 | Split(Box, Box, Box), 52 | Call(Box, Box), 53 | } 54 | 55 | impl Session { 56 | pub fn enumerate(max_depth: u8, min_width: u8, max_width: u8) -> SessionIter { 57 | let session = if max_depth == 0 { 58 | None 59 | } else { 60 | Some(Session::Done) 61 | }; 62 | SessionIter { 63 | max_depth, 64 | min_width, 65 | max_width, 66 | session, 67 | } 68 | } 69 | } 70 | 71 | impl Display for Session { 72 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 73 | use Session::*; 74 | match self { 75 | Done => write!(f, "Done")?, 76 | Recv(s) => write!(f, "Recv<(), {}>", s)?, 77 | Send(s) => write!(f, "Send<(), {}>", s)?, 78 | Loop(s) => write!(f, "Loop<{}>", s)?, 79 | Split(s, p, q) => write!(f, "Split<{}, {}, {}>", s, p, q)?, 80 | Call(s, p) => write!(f, "Call<{}, {}>", s, p)?, 81 | Choose(cs) => { 82 | let count = cs.len(); 83 | write!(f, "Choose<(")?; 84 | for (i, c) in cs.iter().enumerate() { 85 | write!(f, "{}", c)?; 86 | if i + 1 < count { 87 | write!(f, ", ")?; 88 | } 89 | } 90 | if count == 1 { 91 | write!(f, ",")?; 92 | } 93 | write!(f, ")>")?; 94 | } 95 | Offer(cs) => { 96 | let count = cs.len(); 97 | write!(f, "Offer<(")?; 98 | for (i, c) in cs.iter().enumerate() { 99 | write!(f, "{}", c)?; 100 | if i + 1 < count { 101 | write!(f, ", ")?; 102 | } 103 | } 104 | if count == 1 { 105 | write!(f, ",")?; 106 | } 107 | write!(f, ")>")?; 108 | } 109 | Continue(n) => { 110 | write!(f, "Continue<{}>", n)?; 111 | } 112 | } 113 | Ok(()) 114 | } 115 | } 116 | 117 | #[derive(Clone, Debug)] 118 | pub struct SessionIter { 119 | max_depth: u8, 120 | min_width: u8, 121 | max_width: u8, 122 | session: Option, 123 | } 124 | 125 | impl Iterator for SessionIter { 126 | type Item = Session; 127 | 128 | fn next(&mut self) -> Option { 129 | match self.session { 130 | None => None, 131 | Some(ref mut session) => { 132 | let result = session.clone(); 133 | if !session.step(self.max_depth, self.min_width, self.max_width, 0, 0) { 134 | self.session = None; 135 | } 136 | Some(result) 137 | } 138 | } 139 | } 140 | } 141 | 142 | impl Session { 143 | /// Step this session to the next one in a sequence of enumerated sessions, or return `false` if 144 | /// this is the last session in such a sequence. This is called within the `Iterator` 145 | /// implementation for `SessionIter`. 146 | fn step( 147 | &mut self, 148 | max_depth: u8, // how many type constructors deep can we go? 149 | min_width: u8, // what's the min width of tuples in Offer/Choose? (if > 2, no Split/Call) 150 | max_width: u8, // what's the max width of tuples in Offer/Choose? (if < 2, no Split/Call) 151 | loops: u8, // how many loops are we under (controls Continue)? 152 | productive: u8, // how many loops up is the nearest non-Loop type? 153 | ) -> bool { 154 | use Session::*; 155 | 156 | // Abort if the max depth is zero, because we must produce something of length >= 1 157 | if max_depth == 0 { 158 | return false; 159 | } 160 | 161 | match self { 162 | Done => { 163 | if max_depth <= 1 { 164 | if loops > 0 && productive < loops { 165 | *self = Continue(productive); 166 | } else { 167 | return false; 168 | } 169 | } else { 170 | *self = Recv(Box::new(Done)); 171 | } 172 | } 173 | Recv(s) => { 174 | if !s.step(max_depth - 1, min_width, max_width, loops, 0) { 175 | *self = Send(Box::new(Done)); 176 | } 177 | } 178 | Send(s) => { 179 | if !s.step(max_depth - 1, min_width, max_width, loops, 0) { 180 | *self = Loop(Box::new(Done)); 181 | } 182 | } 183 | Loop(s) => { 184 | if !s.step( 185 | max_depth - 1, 186 | min_width, 187 | max_width, 188 | loops + 1, 189 | productive + 1, 190 | ) { 191 | if min_width <= 3 && 3 <= max_width { 192 | *self = Split(Box::new(Done), Box::new(Done), Box::new(Done)); 193 | } else if min_width <= 2 && 2 <= min_width { 194 | *self = Call(Box::new(Done), Box::new(Done)); 195 | } else { 196 | let mut initial = Vec::with_capacity(max_width.into()); 197 | for _ in 0..min_width { 198 | initial.push(Done); 199 | } 200 | *self = Choose(initial); 201 | } 202 | } 203 | } 204 | Split(s, p, q) => { 205 | if !s.step(max_depth - 1, min_width, max_width, loops, 0) 206 | && !p.step(max_depth - 1, min_width, max_width, loops, 0) 207 | && !q.step(max_depth - 1, min_width, max_width, loops, 0) 208 | { 209 | *self = Call(Box::new(Done), Box::new(Done)); 210 | } 211 | } 212 | Call(s, p) => { 213 | if !s.step(max_depth - 1, min_width, max_width, loops, 0) 214 | && !p.step(max_depth - 1, min_width, max_width, loops, 0) 215 | { 216 | let mut initial = Vec::with_capacity(max_width.into()); 217 | for _ in 0..min_width { 218 | initial.push(Done); 219 | } 220 | *self = Choose(initial); 221 | } 222 | } 223 | Choose(cs) => { 224 | for c in cs.iter_mut() { 225 | if c.step(max_depth - 1, min_width, max_width, loops, 0) { 226 | return true; 227 | } else { 228 | *c = Done; 229 | } 230 | } 231 | if cs.len() < max_width.into() { 232 | cs.push(Done); 233 | } else { 234 | let mut initial = Vec::with_capacity(max_width.into()); 235 | for _ in 0..min_width { 236 | initial.push(Done); 237 | } 238 | *self = Offer(initial); 239 | } 240 | } 241 | Offer(cs) => { 242 | for c in cs.iter_mut() { 243 | if c.step(max_depth - 1, min_width, max_width, loops, 0) { 244 | return true; 245 | } else { 246 | *c = Done; 247 | } 248 | } 249 | if cs.len() < max_width.into() { 250 | cs.push(Done); 251 | } else if loops > 0 && productive < loops { 252 | *self = Continue(productive); 253 | } else { 254 | return false; 255 | } 256 | } 257 | Continue(n) => { 258 | if *n + 1 < loops { 259 | *n += 1; 260 | } else { 261 | return false; 262 | } 263 | } 264 | }; 265 | true 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /dialectic/examples/README.md: -------------------------------------------------------------------------------- 1 | # Session-Typed Example Programs Using [Dialectic](https://crates.io/crates/dialectic) 2 | 3 | **See also: [Dialectic's online documentation](https://docs.rs/dialectic), which includes a 4 | step-by-step tutorial.** 5 | 6 | ## What are these? 7 | 8 | Each example is a demonstration of a particular different session type and how the two ends of that 9 | session might be implemented using Dialectic. The examples in this repository are all of a 10 | client/server architecture using TCP sockets and binary serialization for communication (this common 11 | functionality being implemented in [`common.rs`](common.rs)). 12 | 13 | ## Reading and editing the examples 14 | 15 | The examples live in the `examples` directory, one file per example. Both the server and the client 16 | for that example are defined in the same file, as the functions `server` and `client`. Currently, 17 | the list of examples, in rough order of increasing sophistication, is: 18 | 19 | - [`template`](template.rs): The shortest session-typed program: this session performs no 20 | communication. 21 | - [`hello`](hello.rs): Hello world in session types: a send followed by a receive. 22 | - [`tally`](tally.rs): A more complex example: the server tallies input numbers either by 23 | summing them or taking their product. This example demonstrates nested loops, offering and 24 | choosing different protocol options, abstracting out subroutines, and control flow operations. 25 | - [`stack`](stack.rs): A context-free session type: a server which stores a stack of 26 | strings and pops them when requested, but uppercase. This example demonstrates how session types 27 | can be used to make it a type error to pop from the empty stack. 28 | 29 | ## Running the examples 30 | 31 | Each example builds a single binary which can run either the server or the client for that example 32 | depending on whether you invoke its `server` or `client` subcommand from the shell. To run an 33 | example, first start its server: 34 | 35 | ```bash 36 | cargo run --quiet --features="bincode" --example hello -- server 37 | ``` 38 | 39 | Then, in another terminal window, run the client for that example: 40 | 41 | ```bash 42 | cargo run --quiet --features="bincode" --example hello -- client 43 | ``` 44 | 45 | You can quit the server by pressing `^C`. 46 | 47 | Both the client and server default to connecting to / serving on `127.0.0.1:5000`. This can be 48 | altered using the `--address` and `--port` flags. In particular, if you want to expose the server to 49 | another machine, you'll need to specify the address to bind. 50 | 51 | ⚠️ **These examples have been built for pedagogy, not engineered for robustness against malicious 52 | input. Running them connected to the open internet might be a bad idea!** 53 | 54 | ## Creating a new example 55 | 56 | Run the script `./new-example` _from the project root_, using some new example name for 57 | `my_new_example`: 58 | 59 | ```bash 60 | ./new-example my_new_example 61 | ``` 62 | 63 | This will create `examples/my_new_example.rs` by copying `examples/template.rs` and adding a new 64 | `[[example]]` clause to the project's `Cargo.toml`. 65 | 66 | ## Contributing 67 | 68 | Pull requests adding new examples are absolutely welcome! Your PR should also modify this 69 | `README.md` file to update the list of examples with a brief description of your new example. 70 | 71 | Also encouraged are issues explaining a protocol you'd like to see encoded with session types, even 72 | if you can't figure out how to encode it. We can figure it out together! 73 | -------------------------------------------------------------------------------- /dialectic/examples/common.rs: -------------------------------------------------------------------------------- 1 | use dialectic::prelude::*; 2 | use dialectic_tokio_serde::codec::LengthDelimitedCodec; 3 | use dialectic_tokio_serde_bincode::Bincode; 4 | 5 | use colored::*; 6 | use std::{error::Error, fmt::Debug, future::Future, io, process}; 7 | use structopt::StructOpt; 8 | use tokio::{ 9 | io::{AsyncBufRead, AsyncBufReadExt, AsyncWrite, AsyncWriteExt, BufReader, Stdin, Stdout}, 10 | net::{ 11 | tcp::{OwnedReadHalf, OwnedWriteHalf}, 12 | TcpListener, TcpStream, 13 | }, 14 | }; 15 | 16 | /// Prompt the user for a line of input, looping until they enter something that parses. 17 | #[allow(unused)] 18 | pub async fn prompt( 19 | prompt: &str, 20 | input: &mut R, 21 | output: &mut W, 22 | mut parse: impl FnMut(&str) -> Result, 23 | ) -> io::Result 24 | where 25 | R: AsyncBufRead + Unpin, 26 | W: AsyncWrite + Unpin, 27 | { 28 | loop { 29 | output.write_all(prompt.as_bytes()).await?; 30 | output.flush().await?; 31 | let mut line = String::new(); 32 | if 0 == input.read_line(&mut line).await? { 33 | break Err(io::Error::new( 34 | io::ErrorKind::UnexpectedEof, 35 | "unexpected end of input", 36 | )); 37 | } 38 | match parse(line.trim()) { 39 | Ok(t) => break Ok(t), 40 | Err(_) => continue, 41 | } 42 | } 43 | } 44 | 45 | /// A session-typed channel over TCP using length-delimited bincode encoding for serialization. 46 | pub type TcpChan = dialectic_tokio_serde::SymmetricalChan< 47 | S, 48 | Bincode, 49 | LengthDelimitedCodec, 50 | OwnedWriteHalf, 51 | OwnedReadHalf, 52 | >; 53 | 54 | /// Wrap a raw TCP socket in a given session type, using the length delimited bincode transport 55 | /// format/encoding. 56 | fn wrap_socket

(socket: TcpStream, max_length: usize) -> TcpChan

57 | where 58 | P: Session, 59 | { 60 | let (rx, tx) = socket.into_split(); 61 | let (tx, rx) = dialectic_tokio_serde_bincode::length_delimited(tx, rx, 4, max_length); 62 | P::wrap(tx, rx) 63 | } 64 | 65 | /// Options for a TCP app. 66 | #[derive(Debug, Clone, StructOpt)] 67 | struct TcpAppOptions { 68 | #[structopt(subcommand)] 69 | party: Party, 70 | } 71 | 72 | /// A session-typed client/server application using TCP. 73 | #[derive(Debug, Clone, StructOpt)] 74 | enum Party { 75 | /// Run the client for this session 76 | Client { 77 | /// Connect to a server running on this port 78 | #[structopt(short, long, default_value = "5000")] 79 | port: u16, 80 | /// Connect to this IP address 81 | #[structopt(short, long, default_value = "127.0.0.1")] 82 | address: String, 83 | }, 84 | /// Run the server for this session 85 | Server { 86 | /// Serve on this port 87 | #[structopt(short, long, default_value = "5000")] 88 | port: u16, 89 | /// Serve on this IP address 90 | #[structopt(short, long, default_value = "127.0.0.1")] 91 | address: String, 92 | }, 93 | } 94 | 95 | /// Given client and server functions for a given session type `P`, construct a simple app that 96 | /// behaves as either a client or a server, depending on its command line arguments, and 97 | /// communicates over TCP. 98 | pub async fn demo( 99 | server: &'static Server, 100 | client: &'static Client, 101 | max_length: usize, 102 | ) where 103 | P: Session, 104 | P::Dual: Session, 105 | Server: Fn(TcpChan

) -> ServerFuture + Sync + 'static, 106 | Client: Fn(BufReader, Stdout, TcpChan) -> ClientFuture + Sync + 'static, 107 | ServerFuture: 108 | Future>> + std::marker::Send + 'static, 109 | ClientFuture: 110 | Future>> + std::marker::Send + 'static, 111 | ServerResult: Debug, 112 | ClientResult: Debug, 113 | { 114 | use Party::*; 115 | let options = TcpAppOptions::from_args(); 116 | if let Err(e) = match options.party { 117 | Server { port, ref address } => { 118 | listen_on::(address, port, max_length, server).await 119 | } 120 | Client { port, ref address } => { 121 | connect_to::(address, port, max_length, client).await 122 | } 123 | } { 124 | let party_name = match options.party { 125 | Server { .. } => "server", 126 | Client { .. } => "client", 127 | }; 128 | eprintln!("{}", format!("[{}] Error: {}", party_name, e).red()); 129 | process::exit(1); 130 | } 131 | } 132 | 133 | async fn listen_on( 134 | address: &str, 135 | port: u16, 136 | max_length: usize, 137 | interaction: &'static F, 138 | ) -> Result<(), Box> 139 | where 140 | F: Fn(TcpChan

) -> Fut + Sync + 'static, 141 | Fut: Future>> + std::marker::Send, 142 | P: Session, 143 | T: Debug, 144 | { 145 | let listener = TcpListener::bind((address, port)).await?; 146 | println!( 147 | "{}", 148 | format!("[server] Listening on {:?}", listener.local_addr().unwrap()).blue() 149 | ); 150 | loop { 151 | let (socket, addr) = listener.accept().await?; 152 | tokio::spawn(async move { 153 | let _ = interaction(wrap_socket::

(socket, max_length)) 154 | .await 155 | .map(|result| { 156 | println!( 157 | "{}", 158 | format!("[server] {} - Result: {:?}", addr, result).green() 159 | ) 160 | }) 161 | .map_err(|err| { 162 | eprintln!("{}", format!("[server] {} - Error: {}", addr, err).red()) 163 | }); 164 | }); 165 | } 166 | } 167 | 168 | async fn connect_to( 169 | address: &str, 170 | port: u16, 171 | max_length: usize, 172 | interaction: &'static F, 173 | ) -> Result<(), Box> 174 | where 175 | F: Fn(BufReader, Stdout, TcpChan

) -> Fut + Sync + 'static, 176 | Fut: Future>> + std::marker::Send, 177 | P: Session, 178 | T: Debug, 179 | { 180 | let socket = TcpStream::connect((address, port)).await?; 181 | println!( 182 | "{}", 183 | format!( 184 | "[client] Connected to {:?} from {:?}", 185 | socket.peer_addr().unwrap(), 186 | socket.local_addr().unwrap() 187 | ) 188 | .blue() 189 | ); 190 | let stdin = BufReader::new(tokio::io::stdin()); 191 | let stdout = tokio::io::stdout(); 192 | let result = interaction(stdin, stdout, wrap_socket::

(socket, max_length)).await?; 193 | println!("{}", format!("[client] Result: {:?}", result).green()); 194 | Ok(()) 195 | } 196 | -------------------------------------------------------------------------------- /dialectic/examples/hello.rs: -------------------------------------------------------------------------------- 1 | use dialectic::prelude::*; 2 | use std::error::Error; 3 | use tokio::io::{AsyncWriteExt, BufReader, Stdin, Stdout}; 4 | 5 | mod common; 6 | use common::{demo, prompt}; 7 | 8 | #[tokio::main] 9 | async fn main() { 10 | demo::(&server, &client, usize::MAX).await; 11 | } 12 | 13 | /// The session from the client's perspective. 14 | type Client = Session! { 15 | send String; 16 | recv String; 17 | }; 18 | 19 | /// The implementation of the client. 20 | #[Transmitter(Tx for ref str)] 21 | #[Receiver(Rx for String)] 22 | async fn client( 23 | mut input: BufReader, 24 | mut output: Stdout, 25 | chan: Chan, 26 | ) -> Result<(), Box> 27 | where 28 | Tx::Error: Error + Send, 29 | Rx::Error: Error + Send, 30 | { 31 | let name = prompt("What's your name? ", &mut input, &mut output, |name| { 32 | Ok::<_, ()>(name.to_string()) 33 | }) 34 | .await?; 35 | let chan = chan.send_ref(&name).await?; 36 | let (greeting, chan) = chan.recv().await?; 37 | output.write_all(greeting.as_bytes()).await?; 38 | output.write_all(b"\n").await?; 39 | chan.close(); 40 | Ok(()) 41 | } 42 | 43 | /// The session from the server's perspective. 44 | type Server = ::Dual; 45 | 46 | /// The implementation of the server for each client connection. 47 | #[Transmitter(Tx for ref str)] 48 | #[Receiver(Rx for String)] 49 | async fn server(chan: Chan) -> Result<(), Box> 50 | where 51 | Tx::Error: Error + Send, 52 | Rx::Error: Error + Send, 53 | { 54 | let (name, chan) = chan.recv().await?; 55 | let greeting = format!( 56 | "Hello, {}! Your name is {} characters long.", 57 | name, 58 | name.chars().count() 59 | ); 60 | let chan = chan.send_ref(&greeting).await?; 61 | chan.close(); 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /dialectic/examples/stack.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::type_complexity)] 2 | use dialectic::prelude::*; 3 | use std::{error::Error, future::Future, pin::Pin}; 4 | use tokio::io::{AsyncWriteExt, BufReader, Stdin, Stdout}; 5 | 6 | mod common; 7 | use common::{demo, prompt}; 8 | 9 | #[tokio::main] 10 | async fn main() { 11 | demo::(&server, &client, usize::MAX).await; 12 | } 13 | 14 | /// The session from the client's perspective. 15 | type Client = Session! { 16 | loop { 17 | choose { 18 | 0 => break, 19 | 1 => { 20 | send String; 21 | call { continue }; 22 | recv String; 23 | } 24 | } 25 | } 26 | }; 27 | 28 | /// The implementation of the client. 29 | #[Transmitter(Tx for ref str)] 30 | #[Receiver(Rx for String)] 31 | async fn client( 32 | mut input: BufReader, 33 | mut output: Stdout, 34 | chan: Chan, 35 | ) -> Result<(), Box> 36 | where 37 | Tx::Error: Error + Send, 38 | Rx::Error: Error + Send, 39 | { 40 | // Invoke the recursive client function... 41 | client_rec(0, &mut input, &mut output, chan).await 42 | } 43 | 44 | /// The prompt function for the client. 45 | async fn client_prompt( 46 | input: &mut BufReader, 47 | output: &mut Stdout, 48 | size: usize, 49 | ) -> Result { 50 | prompt( 51 | &format!( 52 | "[size: {}] Type a string to push, or [ENTER] to pop/quit: ", 53 | size 54 | ), 55 | input, 56 | output, 57 | |s| Ok::<_, ()>(s.to_string()), 58 | ) 59 | .await 60 | } 61 | 62 | /// Recursive client implementing the protocol. This is a separate function because it needs to take 63 | /// the additional parameter to track the stack height, and needs to take the input/output by 64 | /// reference instead of by value. This function can't be written in `async fn` style because it is 65 | /// recursive, and current restrictions in Rust mean that recursive functions returning futures must 66 | /// explicitly return a boxed `dyn Future` object. 67 | #[Transmitter(Tx for ref str)] 68 | #[Receiver(Rx for String)] 69 | fn client_rec<'a, Tx, Rx>( 70 | size: usize, 71 | input: &'a mut BufReader, 72 | output: &'a mut Stdout, 73 | mut chan: Chan, 74 | ) -> Pin>> + Send + 'a>> 75 | where 76 | Tx::Error: Error + Send, 77 | Rx::Error: Error + Send, 78 | { 79 | Box::pin(async move { 80 | loop { 81 | chan = { 82 | // Get either a string to push or an instruction to pop ([ENTER]) from user 83 | let string = client_prompt(input, output, size).await?; 84 | if string.is_empty() { 85 | // Break this nested loop (about to go to pop/quit) 86 | break chan.choose::<0>().await?.close(); 87 | } else { 88 | // Push the string to the stack 89 | let chan = chan.choose::<1>().await?.send_ref(&string).await?; 90 | // Recursively do `Client` 91 | let chan = chan 92 | .call(|chan| client_rec(size + 1, input, output, chan)) 93 | .await? 94 | .1 95 | .unwrap(); 96 | // Receive a popped string from the stack 97 | let (string, chan) = chan.recv().await?; 98 | // Print it 99 | output.write_all(string.as_bytes()).await?; 100 | output.write_all(b"\n").await?; 101 | // Repeat the loop 102 | chan 103 | } 104 | } 105 | } 106 | Ok(()) 107 | }) 108 | } 109 | 110 | /// The session from the server's perspective. 111 | type Server = ::Dual; 112 | 113 | /// The implementation of the server for each client connection. This function can't be written in 114 | /// `async fn` style because it is recursive, and current restrictions in Rust mean that recursive 115 | /// functions returning futures must explicitly return a boxed `dyn Future` object. 116 | #[Transmitter(Tx for ref str)] 117 | #[Receiver(Rx for String)] 118 | fn server( 119 | mut chan: Chan, 120 | ) -> Pin>> + Send>> 121 | where 122 | Tx::Error: Error + Send, 123 | Rx::Error: Error + Send, 124 | { 125 | Box::pin(async move { 126 | loop { 127 | chan = offer!(in chan { 128 | // Client doesn't want to push a value 129 | 0 => break chan.close(), 130 | // Client wants to push a value 131 | 1 => { 132 | let (string, chan) = chan.recv().await?; // Receive pushed value 133 | let chan = chan.call(server).await?.1.unwrap(); // Recursively do `Server` 134 | chan.send_ref(&string.to_uppercase()).await? // Send back that pushed value 135 | }, 136 | })?; 137 | } 138 | Ok(()) 139 | }) 140 | } 141 | -------------------------------------------------------------------------------- /dialectic/examples/tally.rs: -------------------------------------------------------------------------------- 1 | use dialectic::prelude::*; 2 | use std::{ 3 | error::Error, 4 | fmt::{self, Display}, 5 | str::FromStr, 6 | }; 7 | use thiserror::Error; 8 | use tokio::io::{AsyncWriteExt, BufReader, Stdin, Stdout}; 9 | 10 | mod common; 11 | use common::{demo, prompt}; 12 | use serde::{Deserialize, Serialize}; 13 | 14 | #[tokio::main] 15 | async fn main() { 16 | demo::(&server, &client, usize::MAX).await; 17 | } 18 | 19 | /// The session from the client's perspective. 20 | // pub type Client = Loop>, Done)>>; 21 | pub type Client = Session! { 22 | loop { 23 | choose { 24 | 0 => { 25 | send Operation; 26 | call ClientTally; 27 | }, 28 | 1 => break, 29 | } 30 | } 31 | }; 32 | 33 | /// The implementation of the client. 34 | #[Transmitter(Tx for Operation, i64)] 35 | #[Receiver(Rx for i64)] 36 | async fn client( 37 | mut input: BufReader, 38 | mut output: Stdout, 39 | mut chan: Chan, 40 | ) -> Result<(), Box> 41 | where 42 | Tx::Error: Error + Send, 43 | Rx::Error: Error + Send, 44 | { 45 | loop { 46 | // Parse a desired operation from the user 47 | chan = if let Ok(operation) = 48 | prompt("Operation (+ or *): ", &mut input, &mut output, str::parse).await 49 | { 50 | let chan = chan.choose::<0>().await?.send(operation).await?; 51 | output 52 | .write_all("Enter numbers (press ENTER to tally):\n".as_bytes()) 53 | .await?; 54 | output.flush().await?; 55 | let (done, chan) = chan 56 | .call(|chan| client_tally(&operation, &mut input, &mut output, chan)) 57 | .await?; 58 | let chan = chan.unwrap(); 59 | if done { 60 | break chan.choose::<1>().await?; 61 | } else { 62 | chan 63 | } 64 | } else { 65 | // End of input, so quit 66 | break chan.choose::<1>().await?; 67 | } 68 | } 69 | .close(); 70 | Ok(()) 71 | } 72 | 73 | /// A sub-routine to tally a sequence of numbers. 74 | // pub type ClientTally = Loop, Recv)>>; 75 | pub type ClientTally = Session! { 76 | loop { 77 | choose { 78 | 0 => send i64, 79 | 1 => { 80 | recv i64; 81 | break; 82 | } 83 | } 84 | } 85 | }; 86 | 87 | /// The implementation of the client's tally subroutine. 88 | #[Transmitter(Tx for Operation, i64)] 89 | #[Receiver(Rx for i64)] 90 | async fn client_tally( 91 | operation: &Operation, 92 | input: &mut BufReader, 93 | output: &mut Stdout, 94 | mut chan: Chan, 95 | ) -> Result> 96 | where 97 | Tx::Error: Error + Send, 98 | Rx::Error: Error + Send, 99 | { 100 | let (done, chan) = loop { 101 | // Parse a desired number from the user 102 | let user_input = prompt(&format!("{} ", operation), input, output, |s| { 103 | let s = s.trim(); 104 | if s.is_empty() || s == "=" { 105 | // Empty line or "=" means finish tally 106 | Ok(None) 107 | } else if let Ok(n) = s.parse() { 108 | // A number means add it to the tally 109 | Ok(Some(n)) 110 | } else { 111 | // Anything else is a parse error 112 | Err(()) 113 | } 114 | }) 115 | .await; 116 | match user_input { 117 | // User wants to add another number to the tally 118 | Ok(Some(n)) => chan = chan.choose::<0>().await?.send(n).await?, 119 | // User wants to finish this tally 120 | Ok(None) | Err(_) => { 121 | let (tally, chan) = chan.choose::<1>().await?.recv().await?; 122 | output 123 | .write_all(format!("= {}\n", tally).as_bytes()) 124 | .await?; 125 | output.flush().await?; 126 | break (user_input.is_err(), chan); 127 | } 128 | } 129 | }; 130 | chan.close(); 131 | Ok(done) 132 | } 133 | 134 | /// The session from the server's perspective. 135 | type Server = ::Dual; 136 | 137 | /// The implementation of the server for each client connection. 138 | #[Transmitter(Tx for i64)] 139 | #[Receiver(Rx for Operation, i64)] 140 | async fn server(mut chan: Chan) -> Result<(), Box> 141 | where 142 | Tx::Error: Error + Send, 143 | Rx::Error: Error + Send, 144 | { 145 | loop { 146 | chan = offer!(in chan { 147 | // Client wants to compute another tally 148 | 0 => { 149 | let (op, chan) = chan.recv().await?; 150 | chan.call(|chan| server_tally(op, chan)).await?.1.unwrap() 151 | }, 152 | // Client wants to quit 153 | 1 => break chan.close(), 154 | })?; 155 | } 156 | Ok(()) 157 | } 158 | 159 | /// The tally subroutine from teh server's perspective. 160 | type ServerTally = ::Dual; 161 | 162 | /// The implementation of the server's tally subroutine. 163 | #[Transmitter(Tx for i64)] 164 | #[Receiver(Rx for Operation, i64)] 165 | async fn server_tally( 166 | op: Operation, 167 | mut chan: Chan, 168 | ) -> Result<(), Box> 169 | where 170 | Tx::Error: Error + Send, 171 | Rx::Error: Error + Send, 172 | { 173 | let mut tally = op.unit(); 174 | loop { 175 | chan = offer!(in chan { 176 | // Client wants to add another number to the tally 177 | 0 => { 178 | let (i, chan) = chan.recv().await?; 179 | tally = op.combine(tally, i); 180 | chan 181 | }, 182 | // Client wants to finish this tally 183 | 1 => { 184 | chan.send(tally).await?.close(); 185 | break Ok(()); 186 | } 187 | })?; 188 | } 189 | } 190 | 191 | /// A tallying operation: either `+` or `*`. 192 | #[derive(Debug, Copy, Clone, Serialize, Deserialize)] 193 | pub enum Operation { 194 | Sum, 195 | Product, 196 | } 197 | 198 | impl Operation { 199 | /// The unit of the operation. 200 | pub fn unit(&self) -> i64 { 201 | match self { 202 | Operation::Sum => 0, 203 | Operation::Product => 1, 204 | } 205 | } 206 | 207 | /// Apply the operation to operands, yielding a new tally. 208 | pub fn combine(&self, x: i64, y: i64) -> i64 { 209 | match self { 210 | Operation::Sum => x + y, 211 | Operation::Product => x * y, 212 | } 213 | } 214 | } 215 | 216 | #[derive(Debug, Copy, Clone, Error)] 217 | #[error("couldn't parse operation")] 218 | pub struct ParseOperationError; 219 | 220 | impl FromStr for Operation { 221 | type Err = ParseOperationError; 222 | 223 | fn from_str(s: &str) -> Result { 224 | match s { 225 | "+" => Ok(Operation::Sum), 226 | "*" => Ok(Operation::Product), 227 | _ => Err(ParseOperationError), 228 | } 229 | } 230 | } 231 | 232 | impl Display for Operation { 233 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 234 | match self { 235 | Operation::Sum => write!(f, "+"), 236 | Operation::Product => write!(f, "*"), 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /dialectic/examples/template.rs: -------------------------------------------------------------------------------- 1 | use dialectic::prelude::*; 2 | use std::error::Error; 3 | use tokio::io::{BufReader, Stdin, Stdout}; 4 | 5 | mod common; 6 | #[allow(unused)] 7 | use common::{demo, prompt}; 8 | 9 | #[tokio::main] 10 | async fn main() { 11 | demo::(&server, &client, usize::MAX).await; 12 | } 13 | 14 | /// The session from the client's perspective. 15 | type Client = Session! { 16 | // Fill this in... 17 | }; 18 | 19 | /// The implementation of the client. 20 | #[Transmitter(Tx for /* add types needed by session here */)] 21 | #[Receiver(Rx for /* add types needed by session here */)] 22 | async fn client( 23 | #[allow(unused)] mut input: BufReader, 24 | #[allow(unused)] mut output: Stdout, 25 | #[allow(unused_mut)] mut chan: Chan, 26 | ) -> Result<(), Box> 27 | where 28 | Tx::Error: Error + Send, 29 | Rx::Error: Error + Send, 30 | { 31 | // Do something with the channel... 32 | chan.close(); 33 | Ok(()) 34 | } 35 | 36 | /// The session from the server's perspective. 37 | type Server = ::Dual; 38 | 39 | /// The implementation of the server for each client connection. 40 | #[Transmitter(Tx for /* add types needed by session here */)] 41 | #[Receiver(Rx for /* add types needed by session here */ )] 42 | async fn server( 43 | #[allow(unused_mut)] mut chan: Chan, 44 | ) -> Result<(), Box> 45 | where 46 | Tx::Error: Error + Send, 47 | Rx::Error: Error + Send, 48 | { 49 | // Do something with the channel... 50 | chan.close(); 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /dialectic/new-example: -------------------------------------------------------------------------------- 1 | #! /bin/env bash 2 | 3 | if [ -n "$1" ]; then 4 | cp "./examples/template.rs" "./examples/$1.rs" 5 | echo >> Cargo.toml 6 | echo '[[example]]' >> Cargo.toml 7 | echo "name = \"$1\"" >> Cargo.toml 8 | echo 'required-features = ["bincode"]' >> Cargo.toml 9 | echo 'Created new example: '"./bin/$1.rs" 10 | else 11 | echo "Please specify the name of the example binary you would like to create." 12 | fi -------------------------------------------------------------------------------- /dialectic/src/backend.rs: -------------------------------------------------------------------------------- 1 | //! The interface implemented by all transport backends for a [`Chan`](crate::Chan). 2 | //! 3 | //! A [`Chan`](crate::Chan) is parameterized by its transmitting channel `Tx` and its 4 | //! receiving channel `Rx`. In order to use a `Chan` to run a session, these underlying channels 5 | //! must implement the traits [`Transmitter`] and [`Receiver`], as well as [`Transmit`](Transmit) 6 | //! and [`Receive`](Receive) for at least the types `T` used in those capacities in any given 7 | //! session. 8 | //! 9 | //! Functions which are generic over their backend will in turn need to specify the bounds 10 | //! [`Transmit`](Transmit) and [`Receive`](Receive) for all `T`s they send and receive, 11 | //! respectively. The [`Transmitter`](macro@crate::Transmitter) and 12 | //! [`Receiver`](macro@crate::Receiver) attribute macros make this bound specification succinct; see 13 | //! their documentation for more details. 14 | 15 | #[doc(no_inline)] 16 | pub use call_by::{By, Convention, Mut, Ref, Val}; 17 | use std::{future::Future, pin::Pin}; 18 | 19 | mod choice; 20 | pub use choice::*; 21 | 22 | /// A backend transport used for transmitting (i.e. the `Tx` parameter of [`Chan`](crate::Chan)) 23 | /// must implement [`Transmitter`], which specifies what type of errors it might return, as well as 24 | /// giving a method to send [`Choice`]s across the channel. This is a super-trait of [`Transmit`], 25 | /// which is what's actually needed to receive particular values over a [`Chan`](crate::Chan). 26 | /// 27 | /// If you're writing a function and need a lot of different [`Transmit`](Transmit) bounds, the 28 | /// [`Transmitter`](macro@crate::Transmitter) attribute macro can help you specify them more 29 | /// succinctly. 30 | pub trait Transmitter { 31 | /// The type of possible errors when sending. 32 | type Error; 33 | 34 | /// Send any `Choice` using the [`Convention`] specified by the trait implementation. 35 | fn send_choice<'async_lifetime, const LENGTH: usize>( 36 | &'async_lifetime mut self, 37 | choice: Choice, 38 | ) -> Pin> + Send + 'async_lifetime>>; 39 | } 40 | 41 | /// A marker trait indicating that some type `T` is transmittable as the associated type 42 | /// `ReceivedAs` in a session type specification. 43 | /// 44 | /// This type is blanket-implemented for *all* sized types such that they are transmitted as 45 | /// themselves; for unsized types such as `str` and `[T]`, there are implementations such that `str` 46 | /// is transmittable and `ReceivedAs = String` and `[T]` is transmittable and `ReceivedAs = Vec`. 47 | /// Backends must choose whether or not to implement `Transmit` for these unsized types, but *must* 48 | /// guarantee that they can be received as their associated `ReceivedAs` type if they are 49 | /// transmittable. 50 | pub trait Transmittable { 51 | /// The equivalent type that receiving this transmitted type will give on the other end of the 52 | /// connection. 53 | type ReceivedAs: Sized; 54 | } 55 | 56 | impl Transmittable for T { 57 | type ReceivedAs = T; 58 | } 59 | 60 | impl Transmittable for str { 61 | type ReceivedAs = String; 62 | } 63 | 64 | impl Transmittable for std::ffi::CStr { 65 | type ReceivedAs = std::ffi::CString; 66 | } 67 | 68 | impl Transmittable for std::ffi::OsStr { 69 | type ReceivedAs = std::ffi::OsString; 70 | } 71 | 72 | impl Transmittable for std::path::Path { 73 | type ReceivedAs = std::path::PathBuf; 74 | } 75 | 76 | impl Transmittable for [T] { 77 | type ReceivedAs = Vec; 78 | } 79 | 80 | /// If a transport is [`Transmit`](Transmit), we can use it to [`send`](Transmit::send) a 81 | /// message of type `T` by [`Val`], [`Ref`], or [`Mut`], depending on the calling convention 82 | /// specified by `C`. Any transmitted type `T` may be received as its associated [`::ReceivedAs`](Transmittable) type; if a backend has `Transmit` implemented 84 | /// for some `T`, it guarantees that `T` will be received on the other side as an equivalent, 85 | /// well-formed `T::ReceivedAs`. 86 | /// 87 | /// If you're writing a function and need a lot of different `Transmit` bounds, the 88 | /// [`Transmitter`](macro@crate::Transmitter) attribute macro can help you specify them more 89 | /// succinctly. 90 | /// 91 | /// # Examples 92 | /// 93 | /// For an example of implementing [`Transmit`], check out the source for the implementation of 94 | /// [`Transmit`] for the [`dialectic_tokio_mpsc::Sender`] type in the [`dialectic_tokio_mpsc`] 95 | /// crate. 96 | /// 97 | /// [`dialectic_tokio_mpsc`]: https://docs.rs/dialectic-tokio-mpsc 98 | /// 99 | /// [`dialectic_tokio_mpsc::Sender`]: 100 | /// https://docs.rs/dialectic-tokio-mpsc/latest/dialectic_tokio_mpsc/struct.Sender.html 101 | pub trait Transmit: Transmitter 102 | where 103 | T: Transmittable, 104 | { 105 | /// Send a message using the [`Convention`] specified by the trait implementation. 106 | fn send<'a, 'async_lifetime>( 107 | &'async_lifetime mut self, 108 | message: >::Type, 109 | ) -> Pin> + Send + 'async_lifetime>> 110 | where 111 | T: By<'a, C>, 112 | 'a: 'async_lifetime; 113 | } 114 | 115 | /// A backend transport used for receiving (i.e. the `Rx` parameter of [`Chan`](crate::Chan)) must 116 | /// implement [`Receiver`], which specifies what type of errors it might return, as well as giving a 117 | /// method to send [`Choice`]s across the channel. This is a super-trait of [`Receive`], which is 118 | /// what's actually needed to receive particular values over a [`Chan`](crate::Chan). 119 | /// 120 | /// If you're writing a function and need a lot of different [`Receive`](Receive) bounds, the 121 | /// [`Receiver`](macro@crate::Receiver) attribute macro can help you specify them more succinctly. 122 | pub trait Receiver { 123 | /// The type of possible errors when receiving. 124 | type Error; 125 | 126 | /// Receive any `Choice`. It is impossible to construct a `Choice<0>`, so if `N = 0`, a 127 | /// [`Receiver::Error`] must be returned. 128 | fn recv_choice<'async_lifetime, const LENGTH: usize>( 129 | &'async_lifetime mut self, 130 | ) -> Pin, Self::Error>> + Send + 'async_lifetime>>; 131 | } 132 | 133 | /// If a transport is [`Receive`](Receive), we can use it to [`recv`](Receive::recv) a message of 134 | /// type `T`. 135 | /// 136 | /// If you're writing a function and need a lot of different [`Receive`](Receive) bounds, the 137 | /// [`Receiver`](macro@crate::Receiver) attribute macro can help you specify them more succinctly. 138 | /// 139 | /// # Examples 140 | /// 141 | /// For an example of implementing [`Receive`], check out the source for the implementation of 142 | /// [`Receive`] for the [`dialectic_tokio_mpsc::Receiver`] type in the [`dialectic_tokio_mpsc`] 143 | /// crate. 144 | /// 145 | /// [`dialectic_tokio_mpsc`]: https://docs.rs/dialectic-tokio-mpsc 146 | /// 147 | /// [`dialectic_tokio_mpsc::Receiver`]: 148 | /// https://docs.rs/dialectic-tokio-mpsc/latest/dialectic_tokio_mpsc/struct.Receiver.html 149 | pub trait Receive: Receiver { 150 | /// Receive a message. This may require type annotations for disambiguation. 151 | fn recv<'async_lifetime>( 152 | &'async_lifetime mut self, 153 | ) -> Pin> + Send + 'async_lifetime>>; 154 | } 155 | -------------------------------------------------------------------------------- /dialectic/src/backend/choice.rs: -------------------------------------------------------------------------------- 1 | use crate::unary::*; 2 | use std::convert::{TryFrom, TryInto}; 3 | use thiserror::Error; 4 | 5 | /// A `Choice` represents a selection between several protocols offered by [`offer!`](crate::offer). 6 | /// 7 | /// It wraps an ordinary non-negative number, with the guarantee that it is *strictly less than* the 8 | /// type level number `N`. 9 | /// 10 | /// Unless you are implementing a [`backend`](crate::backend), you do not need to interact with 11 | /// `Choice`s directly. However, all backends must implement [`Transmit, 12 | /// Val>`](crate::backend::Transmit) and [`Receive>`](crate::backend::Receive) for all `N` 13 | /// in order to support the [`Choose`](crate::Choose) and [`Offer`](crate::Offer) constructs. 14 | /// 15 | /// # Examples 16 | /// 17 | /// It's possible to construct a [`Choice`] from all `u8` strictly less than its type parameter `N`: 18 | /// 19 | /// ``` 20 | /// use std::convert::TryInto; 21 | /// use dialectic::backend::Choice; 22 | /// 23 | /// # fn main() -> Result<(), Box> { 24 | /// let zero: Choice<3> = 0_u8.try_into()?; 25 | /// let one: Choice<3> = 1_u8.try_into()?; 26 | /// let two: Choice<3> = 2_u8.try_into()?; 27 | /// 28 | /// assert_eq!(zero, 0_u8); 29 | /// assert_eq!(one, 1_u8); 30 | /// assert_eq!(two, 2_u8); 31 | /// # Ok(()) 32 | /// # } 33 | /// ``` 34 | /// 35 | /// But we cannot construct a [`Choice`] from a `u8` equal to or greater than its type parameter 36 | /// `N`: 37 | /// 38 | /// ``` 39 | /// # use std::convert::TryInto; 40 | /// # use dialectic::backend::Choice; 41 | /// # 42 | /// let attempted_three: Result, _> = 3_u8.try_into(); 43 | /// let attempted_four: Result, _> = 4_u8.try_into(); 44 | /// 45 | /// assert!(attempted_three.is_err()); 46 | /// assert!(attempted_four.is_err()); 47 | /// ``` 48 | /// 49 | /// Note that this means `Choice<0>` is unrepresentable, because you cannot choose something from a 50 | /// set of zero things: 51 | /// 52 | /// ``` 53 | /// # use std::convert::TryInto; 54 | /// # use dialectic::backend::Choice; 55 | /// # 56 | /// for i in 0 ..= u8::MAX { 57 | /// let attempt: Result, _> = i.try_into(); 58 | /// assert!(attempt.is_err()); 59 | /// } 60 | /// ``` 61 | #[repr(transparent)] 62 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 63 | pub struct Choice { 64 | choice: u8, 65 | } 66 | 67 | // Choice<0> is unconstructable, but for all N, 0 is a Choice: 68 | impl Default for Choice 69 | where 70 | Number: ToUnary>, 71 | { 72 | fn default() -> Self { 73 | 0.try_into() 74 | .expect("0 is in bounds for all non-zero-bounded `Choice`s") 75 | } 76 | } 77 | 78 | impl PartialEq for Choice { 79 | fn eq(&self, other: &u8) -> bool { 80 | self.choice == *other 81 | } 82 | } 83 | 84 | /// When attempting to construct a [`Choice`](Choice) via [`try_into`](TryInto::try_into) or 85 | /// [`try_from`](TryFrom::try_from), this error is thrown when the choice exceeds the type-level 86 | /// strict upper bound `N`. 87 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Error)] 88 | pub struct OutOfBoundsChoiceError { 89 | choice: u8, 90 | bound: usize, 91 | } 92 | 93 | impl std::fmt::Display for OutOfBoundsChoiceError { 94 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 95 | write!( 96 | f, 97 | "choice {} is invalid for exclusive upper bound {}", 98 | self.choice, self.bound 99 | ) 100 | } 101 | } 102 | 103 | impl TryFrom for Choice { 104 | type Error = OutOfBoundsChoiceError; 105 | 106 | fn try_from(choice: u8) -> Result { 107 | if (choice as usize) < N { 108 | Ok(Choice { choice }) 109 | } else { 110 | Err(OutOfBoundsChoiceError { choice, bound: N }) 111 | } 112 | } 113 | } 114 | 115 | impl From> for u8 { 116 | fn from(Choice { choice, .. }: Choice) -> u8 { 117 | choice 118 | } 119 | } 120 | 121 | // If the serde feature is enabled, do custom serialization for `Choice` that fails when receiving 122 | // an out-of-bounds choice. 123 | #[cfg(feature = "serde")] 124 | mod serialization { 125 | use super::*; 126 | 127 | use serde::{ 128 | de::{self, Visitor}, 129 | Deserialize, Deserializer, Serialize, Serializer, 130 | }; 131 | 132 | impl Serialize for Choice { 133 | fn serialize(&self, serializer: S) -> Result 134 | where 135 | S: Serializer, 136 | { 137 | serializer.serialize_u8(self.choice) 138 | } 139 | } 140 | 141 | #[derive(Debug, Clone, Copy)] 142 | struct ChoiceVisitor; 143 | 144 | impl Default for ChoiceVisitor { 145 | fn default() -> Self { 146 | ChoiceVisitor 147 | } 148 | } 149 | 150 | impl<'de, const N: usize> Visitor<'de> for ChoiceVisitor { 151 | type Value = Choice; 152 | 153 | fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 154 | write!( 155 | f, 156 | "a non-negative integer strictly less than {}", 157 | N.min(u8::MAX as usize) 158 | )?; 159 | if N == 0 { 160 | write!( 161 | f, 162 | " (since that strict upper bound is 0, this is impossible)" 163 | )?; 164 | } 165 | Ok(()) 166 | } 167 | 168 | // Only `visit_u64` is implemented because all the other `visit_u*` methods forward to this 169 | // one by default, per the serde documentation. 170 | fn visit_u64(self, v: u64) -> Result 171 | where 172 | E: de::Error, 173 | { 174 | let choice: u8 = v 175 | .try_into() 176 | .map_err(|_| de::Error::invalid_value(de::Unexpected::Unsigned(v), &self))?; 177 | choice.try_into().map_err(|_| { 178 | de::Error::invalid_value(de::Unexpected::Unsigned(choice as u64), &self) 179 | }) 180 | } 181 | } 182 | 183 | impl<'de, const N: usize> Deserialize<'de> for Choice { 184 | fn deserialize(deserializer: D) -> Result, D::Error> 185 | where 186 | D: Deserializer<'de>, 187 | { 188 | let visitor: ChoiceVisitor = ChoiceVisitor::default(); 189 | deserializer.deserialize_u8(visitor) 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /dialectic/src/error.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] // To link with documentation 2 | use crate::prelude::*; 3 | 4 | /// A placeholder for a missing [`Transmit`] or [`Receive`] end of a connection. 5 | /// 6 | /// When using [`split`](Chan::split), the resultant two channels can only send or only receive, 7 | /// respectively. This is reflected at the type level by the presence of [`Unavailable`] on the type 8 | /// of the connection which *is not* present for each part of the split. 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 10 | pub struct Unavailable { 11 | _priv: (), 12 | } 13 | 14 | /// The error returned when a closure which is expected to complete a channel's session fails to 15 | /// finish the session of the channel it is given. 16 | /// 17 | /// This error can arise either if the channel is dropped *before* its session is completed, or if 18 | /// it is stored somewhere and is dropped *after* the closure's future is finished. The best way to 19 | /// ensure this error does not occur is to call [`close`](Chan::close) on the channel, 20 | /// which statically ensures it is dropped exactly when the session is complete. 21 | #[derive(Derivative)] 22 | #[derivative(Debug(bound = ""))] 23 | pub enum SessionIncomplete { 24 | /// Both the sending half `Tx` and the receiving half `Rx` did not complete the session 25 | /// correctly. 26 | BothHalves { 27 | /// The incomplete sending half: [`Unfinished`](IncompleteHalf::Unfinished) if dropped 28 | /// before the end of the session, [`Unclosed`](IncompleteHalf::Unclosed) if not dropped 29 | /// after the end of the session. 30 | tx: IncompleteHalf, 31 | /// The incomplete receiving half: [`Unfinished`](IncompleteHalf::Unfinished) if dropped 32 | /// before the end of the session, [`Unclosed`](IncompleteHalf::Unclosed) if not dropped 33 | /// after the end of the session. 34 | rx: IncompleteHalf, 35 | }, 36 | /// Only the sending half `Tx` did not complete the session correctly, but the receiving half 37 | /// `Rx` did complete it correctly. 38 | TxHalf { 39 | /// The incomplete sending half: [`Unfinished`](IncompleteHalf::Unfinished) if dropped 40 | /// before the end of the session, [`Unclosed`](IncompleteHalf::Unclosed) if not dropped 41 | /// after the end of the session. 42 | tx: IncompleteHalf, 43 | /// The receiving half, whose session was completed. 44 | #[derivative(Debug = "ignore")] 45 | rx: Rx, 46 | }, 47 | /// Only the receiving half `Rx` did not complete the session correctly, but the sending half 48 | /// `Tx` did complete it correctly. 49 | RxHalf { 50 | /// The sending half, whose session was completed. 51 | #[derivative(Debug = "ignore")] 52 | tx: Tx, 53 | /// The incomplete receiving half: [`Unfinished`](IncompleteHalf::Unfinished) if dropped 54 | /// before the end of the session, [`Unclosed`](IncompleteHalf::Unclosed) if not dropped 55 | /// after the end of the session. 56 | rx: IncompleteHalf, 57 | }, 58 | } 59 | 60 | /// A representation of what has gone wrong when a connection half `Tx` or `Rx` is incomplete. 61 | #[derive(Derivative)] 62 | #[derivative(Debug(bound = ""))] 63 | pub enum IncompleteHalf { 64 | /// The underlying channel was dropped before the session was `Done`. 65 | Unfinished(#[derivative(Debug = "ignore")] T), 66 | /// The underlying channel was not dropped or [`close`](Chan::close)d after the session 67 | /// was `Done`. 68 | Unclosed, 69 | } 70 | 71 | impl std::error::Error for IncompleteHalf {} 72 | 73 | impl std::fmt::Display for IncompleteHalf { 74 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 75 | write!(f, "incomplete session or sub-session: channel half ")?; 76 | write!( 77 | f, 78 | "{}", 79 | match self { 80 | IncompleteHalf::Unfinished(_) => "was dropped before the session was `Done`", 81 | IncompleteHalf::Unclosed => "was not closed after the session was `Done`", 82 | } 83 | ) 84 | } 85 | } 86 | 87 | impl SessionIncomplete { 88 | /// Extract the send and receive halves `Tx` and `Rx`, if they are present, from this 89 | /// `SessionIncomplete` error. 90 | pub fn into_halves( 91 | self, 92 | ) -> ( 93 | Result>, 94 | Result>, 95 | ) { 96 | match self { 97 | SessionIncomplete::BothHalves { tx, rx } => (Err(tx), Err(rx)), 98 | SessionIncomplete::TxHalf { tx, rx } => (Err(tx), Ok(rx)), 99 | SessionIncomplete::RxHalf { tx, rx } => (Ok(tx), Err(rx)), 100 | } 101 | } 102 | } 103 | 104 | impl std::error::Error for SessionIncomplete {} 105 | 106 | impl std::fmt::Display for SessionIncomplete { 107 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 108 | use IncompleteHalf::*; 109 | write!(f, "incomplete session or sub-session: channel")?; 110 | let reason = match self { 111 | SessionIncomplete::BothHalves { tx, rx } => match (tx, rx) { 112 | (Unclosed, Unclosed) => " was not closed after the session was `Done`", 113 | (Unclosed, Unfinished(_)) => { 114 | "'s sending half was not closed after the session was `Done` \ 115 | and its receiving half was dropped before the session was `Done`" 116 | } 117 | (Unfinished(_), Unclosed) => { 118 | "'s sending half was dropped before the session was `Done` \ 119 | and its receiving half was not closed after the session was `Done`" 120 | } 121 | (Unfinished(_), Unfinished(_)) => " was dropped before the session was `Done`", 122 | }, 123 | SessionIncomplete::TxHalf { tx, .. } => match tx { 124 | Unfinished(_) => "'s sending half was dropped before the session was `Done`", 125 | Unclosed => "'s sending half was not closed after the session was `Done`", 126 | }, 127 | SessionIncomplete::RxHalf { rx, .. } => match rx { 128 | Unfinished(_) => "'s receiving half was dropped before the session was `Done`", 129 | Unclosed => "'s receiving half was not closed after the session was `Done`", 130 | }, 131 | }; 132 | write!(f, "{}", reason) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /dialectic/src/session.rs: -------------------------------------------------------------------------------- 1 | pub use crate::chan::Over; 2 | 3 | use crate::types::{Actionable, HasDual, Scoped}; 4 | use crate::Chan; 5 | use std::future::Future; 6 | 7 | /// The `Session` extension trait gives methods to create session-typed channels from session types. 8 | /// These are implemented as static methods on the session type itself. 9 | /// 10 | /// This trait is already implemented for all valid session types, and cannot be extended by users 11 | /// of this crate. 12 | /// 13 | /// # Examples 14 | /// 15 | /// ``` 16 | /// use dialectic::prelude::*; 17 | /// use dialectic_tokio_mpsc as mpsc; 18 | /// 19 | /// let (c1, c2) = ::channel(mpsc::unbounded_channel); 20 | /// // do something with these channels... 21 | /// ``` 22 | /// 23 | /// # Counterexamples 24 | /// 25 | /// It is only possible to create a session-typed channel when the session type is valid. The 26 | /// following examples fail, for the reasons described: 27 | /// 28 | /// 1. The session type `send &'a str` for a non-static `'a` is not `'static`, but all 29 | /// session types must be `'static`: 30 | /// 31 | /// ```compile_fail 32 | /// # use dialectic::prelude::*; 33 | /// # use dialectic_tokio_mpsc as mpsc; 34 | /// fn something<'a>(_: &'a str) { 35 | /// let (c1, c2) = ::channel(mpsc::unbounded_channel); 36 | /// } 37 | /// ``` 38 | /// 39 | /// 2. The session type `Loop>` is not `Scoped`, because `Continue<1>` must occur 40 | /// within two nested [`Loop`]s to be properly scoped: 41 | /// 42 | /// ```compile_fail 43 | /// # use dialectic::prelude::*; 44 | /// # use dialectic_tokio_mpsc as mpsc; 45 | /// use dialectic::types::{Loop, Continue}; 46 | /// use dialectic::unary::types::*; 47 | /// let (c1, c2) = >>::channel(mpsc::unbounded_channel); 48 | /// ``` 49 | /// 50 | /// Note that you cannot write out an ill-scoped session type using the 51 | /// [`Session!`](crate::Session@macro) macro, because it will throw an error if you try. 52 | /// 53 | /// 3. The session type `loop {}` is not `Actionable` because it is an "unproductive" 54 | /// infinite loop, where no matter how many times you loop, there will never be an available 55 | /// action to perform on the channel: 56 | /// 57 | /// ```compile_fail 58 | /// # use dialectic::prelude::*; 59 | /// # use dialectic_tokio_mpsc as mpsc; 60 | /// let (c1, c2) = ::channel(mpsc::unbounded_channel); 61 | /// ``` 62 | /// 63 | /// [`Loop`]: crate::types::Loop 64 | pub trait Session 65 | where 66 | Self: Scoped 67 | + Actionable::Action> 68 | + HasDual::Dual>, 69 | { 70 | /// The dual to this session type, i.e. the session type for the other side of this channel. 71 | /// 72 | /// Every individual session type component has a dual defined by [`HasDual`]. This is that 73 | /// type. 74 | type Dual; 75 | 76 | /// The canonical next channel action for this session type. 77 | /// 78 | /// For [`Send`], [`Recv`], [`Offer`], [`Choose`], [`Split`], [`Call`], and [`Done`], the next 79 | /// channel action is the session type itself. For [`Loop`], the next channel action is the 80 | /// inside of the loop, with all [`Continue`]s within it appropriately unrolled by one loop 81 | /// iteration. 82 | /// 83 | /// This is always the action type defined by [`Actionable`] for this session type. 84 | /// 85 | /// [`Send`]: crate::types::Send 86 | /// [`Recv`]: crate::types::Recv 87 | /// [`Offer`]: crate::types::Offer 88 | /// [`Choose`]: crate::types::Choose 89 | /// [`Split`]: crate::types::Split 90 | /// [`Call`]: crate::types::Call 91 | /// [`Done`]: crate::types::Done 92 | /// [`Loop`]: crate::types::Loop 93 | /// [`Continue`]: crate::types::Continue 94 | type Action; 95 | 96 | /// Given a closure which generates a uni-directional underlying transport channel, create a 97 | /// pair of dual [`Chan`]s which communicate over the transport channels resulting from these 98 | /// closures. 99 | /// 100 | /// By internally wiring together the two directional channels, this function assures that 101 | /// communications over the channels actually follow the session specified. 102 | /// 103 | /// # Examples 104 | /// 105 | /// ``` 106 | /// use dialectic::prelude::*; 107 | /// use dialectic_tokio_mpsc as mpsc; 108 | /// 109 | /// # #[tokio::main] 110 | /// # async fn main() { 111 | /// let (c1, c2) = ::channel(mpsc::unbounded_channel); 112 | /// # } 113 | /// ``` 114 | fn channel( 115 | mut make: impl FnMut() -> (Tx, Rx), 116 | ) -> (Chan, Chan) 117 | where 118 | ::Dual: Scoped + Actionable + HasDual, 119 | Tx: Send + 'static, 120 | Rx: Send + 'static, 121 | { 122 | let (tx0, rx0) = make(); 123 | let (tx1, rx1) = make(); 124 | (Self::wrap(tx0, rx1), ::wrap(tx1, rx0)) 125 | } 126 | 127 | /// Given two closures, each of which generates a uni-directional underlying transport channel, 128 | /// create a pair of dual [`Chan`]s which communicate over the transport channels resulting from 129 | /// these closures. 130 | /// 131 | /// By internally wiring together the two directional channels, this function assures that 132 | /// communications over the channels actually follow the session specified. 133 | /// 134 | /// # Examples 135 | /// 136 | /// ``` 137 | /// use dialectic::prelude::*; 138 | /// use dialectic_tokio_mpsc as mpsc; 139 | /// 140 | /// # #[tokio::main] 141 | /// # async fn main() { 142 | /// let (c1, c2) = ::bichannel( 143 | /// mpsc::unbounded_channel, 144 | /// || mpsc::channel(1), 145 | /// ); 146 | /// # } 147 | /// ``` 148 | fn bichannel( 149 | make0: impl FnOnce() -> (Tx0, Rx0), 150 | make1: impl FnOnce() -> (Tx1, Rx1), 151 | ) -> (Chan, Chan) 152 | where 153 | ::Dual: Scoped + Actionable + HasDual, 154 | Tx0: Send + 'static, 155 | Rx0: Send + 'static, 156 | Tx1: Send + 'static, 157 | Rx1: Send + 'static, 158 | { 159 | let (tx0, rx0) = make0(); 160 | let (tx1, rx1) = make1(); 161 | (Self::wrap(tx0, rx1), ::wrap(tx1, rx0)) 162 | } 163 | 164 | /// Given a transmitting and receiving end of an un-session-typed connection, wrap them in a new 165 | /// session-typed channel for the protocol `P.` 166 | /// 167 | /// It is expected that the other ends of these connections will be wrapped in a channel with 168 | /// the [`Dual`](crate::Session::Dual) session type. 169 | /// 170 | /// # Examples 171 | /// 172 | /// ``` 173 | /// use dialectic::prelude::*; 174 | /// use dialectic_tokio_mpsc as mpsc; 175 | /// 176 | /// # #[tokio::main] 177 | /// # async fn main() { 178 | /// let (tx, rx) = mpsc::unbounded_channel(); 179 | /// let c = ::wrap(tx, rx); 180 | /// c.close(); 181 | /// # } 182 | /// ``` 183 | fn wrap(tx: Tx, rx: Rx) -> Chan 184 | where 185 | Tx: Send + 'static, 186 | Rx: Send + 'static, 187 | { 188 | Chan::from_raw_unchecked(tx, rx) 189 | } 190 | 191 | /// Given a closure which runs the session on a channel from start to completion, run that 192 | /// session on the given pair of sending and receiving connections. 193 | /// 194 | /// # Errors 195 | /// 196 | /// The closure must *finish* the session `P` on the channel given to it and *drop* the finished 197 | /// channel before the future returns. If the channel is dropped before completing `P` or is not 198 | /// dropped after completing `P`, a [`SessionIncomplete`](crate::SessionIncomplete) error will 199 | /// be returned instead of a channel for `Q`. The best way to ensure this error does not occur 200 | /// is to call [`close`](Chan::close) on the channel before returning from the future, because 201 | /// this statically checks that the session is complete and drops the channel. 202 | /// 203 | /// # Examples 204 | /// 205 | /// ``` 206 | /// use dialectic::prelude::*; 207 | /// use dialectic_tokio_mpsc as mpsc; 208 | /// 209 | /// # #[tokio::main] 210 | /// # async fn main() -> Result<(), Box> { 211 | /// let (tx, rx) = mpsc::unbounded_channel(); 212 | /// let (output, ends) = ::over(tx, rx, |chan| async move { 213 | /// chan.close(); 214 | /// Ok::<_, mpsc::Error>("Hello!".to_string()) 215 | /// }).await; 216 | /// 217 | /// assert_eq!(output?, "Hello!"); 218 | /// let (tx, rx) = ends?; 219 | /// # Ok(()) 220 | /// # } 221 | /// ``` 222 | /// 223 | /// As noted above, an error is thrown when the channel is dropped too early: 224 | /// 225 | /// ``` 226 | /// # use dialectic::prelude::*; 227 | /// # use dialectic_tokio_mpsc as mpsc; 228 | /// # 229 | /// # #[tokio::main] 230 | /// # async fn main() -> Result<(), Box> { 231 | /// use dialectic::SessionIncomplete::BothHalves; 232 | /// use dialectic::IncompleteHalf::Unfinished; 233 | /// 234 | /// let (tx, rx) = mpsc::unbounded_channel(); 235 | /// let (_, ends) = ::over(tx, rx, |chan| async move { 236 | /// Ok::<_, mpsc::Error>(()) 237 | /// }).await; 238 | /// 239 | /// assert!(matches!(ends, Err(BothHalves { tx: Unfinished(_), rx: Unfinished(_) }))); 240 | /// # Ok(()) 241 | /// # } 242 | /// ``` 243 | /// 244 | /// And likewise, an error is thrown when the channel is not dropped by the end of the future: 245 | /// 246 | /// ``` 247 | /// # use dialectic::prelude::*; 248 | /// # use dialectic_tokio_mpsc as mpsc; 249 | /// # 250 | /// # #[tokio::main] 251 | /// # async fn main() -> Result<(), Box> { 252 | /// use std::sync::{Arc, Mutex}; 253 | /// 254 | /// use dialectic::SessionIncomplete::BothHalves; 255 | /// use dialectic::IncompleteHalf::Unclosed; 256 | /// 257 | /// // 🚨 DON'T DO THIS! 🚨 258 | /// // We'll put the `Chan` here so it outlives the closure 259 | /// let hold_on_to_chan = Arc::new(Mutex::new(None)); 260 | /// let hold = hold_on_to_chan.clone(); 261 | /// 262 | /// let (tx, rx) = mpsc::unbounded_channel(); 263 | /// let (_, ends) = ::over(tx, rx, |chan| async move { 264 | /// *hold.lock().unwrap() = Some(chan); 265 | /// Ok::<_, mpsc::Error>(()) 266 | /// }).await; 267 | /// 268 | /// assert!(matches!(ends, Err(BothHalves { tx: Unclosed, rx: Unclosed }))); 269 | /// 270 | /// // 🚨 DON'T DO THIS! 🚨 271 | /// // Make sure the `Chan` outlives the closure by holding onto it until here 272 | /// drop(hold_on_to_chan); 273 | /// # Ok(()) 274 | /// # } 275 | /// ``` 276 | fn over(tx: Tx, rx: Rx, with_chan: F) -> Over 277 | where 278 | Tx: Send + 'static, 279 | Rx: Send + 'static, 280 | F: FnOnce(Chan) -> Fut, 281 | Fut: Future, 282 | { 283 | crate::chan::over::(tx, rx, with_chan) 284 | } 285 | } 286 | 287 | impl Session for S { 288 | type Dual = ::DualSession; 289 | type Action = ::NextAction; 290 | } 291 | -------------------------------------------------------------------------------- /dialectic/src/tuple.rs: -------------------------------------------------------------------------------- 1 | //! Conversions back and forth between flat tuples and their corresponding inductive list 2 | //! structures. 3 | //! 4 | //! Internally, this library uses inductive type-level lists, but presents an external interface in 5 | //! terms of tuples, for readability. The traits here convert between the two equivalent 6 | //! representations. 7 | //! 8 | //! At present, tuples up to size 128 are supported. 9 | 10 | use super::unary::*; 11 | 12 | /// Convert a tuple into its corresponding inductive list structure. 13 | pub trait Tuple: Sized { 14 | /// The corresponding inductive list. 15 | type AsList: List; 16 | } 17 | 18 | /// Convert an inductive list structure into its corresponding tuple. 19 | pub trait List: Sized { 20 | /// The corresponding tuple. 21 | type AsTuple: Tuple; 22 | } 23 | 24 | /// Take the length of a type-level list as a unary type-level number. 25 | pub trait HasLength { 26 | /// The length of a type-level list. 27 | type Length: Unary; 28 | } 29 | 30 | impl HasLength for () { 31 | type Length = Z; 32 | } 33 | 34 | impl HasLength for (T, Ts) { 35 | type Length = S; 36 | } 37 | 38 | dialectic_macro::impl_tuples!(256); 39 | -------------------------------------------------------------------------------- /dialectic/src/types/call.rs: -------------------------------------------------------------------------------- 1 | use std::{any::Any, marker::PhantomData}; 2 | 3 | use super::sealed::IsSession; 4 | use super::*; 5 | 6 | /// Call the session `P` as a subroutine using [`call`](crate::Chan::call), then do the session `Q`. 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | pub struct Call(PhantomData P>, PhantomData Q>); 9 | 10 | impl Default for Call { 11 | fn default() -> Self { 12 | Call(PhantomData, PhantomData) 13 | } 14 | } 15 | 16 | impl IsSession for Call {} 17 | 18 | impl HasDual for Call { 19 | type DualSession = Call; 20 | } 21 | 22 | impl Actionable for Call { 23 | type NextAction = Self; 24 | } 25 | 26 | impl, Q: Scoped> Scoped for Call {} 27 | 28 | impl, Q: Subst, R> Subst for Call { 29 | type Substituted = Call; 30 | } 31 | 32 | impl, R> Then for Call { 33 | type Combined = Call; 34 | } 35 | 36 | impl, Q: Lift> Lift for Call { 37 | type Lifted = Call; 38 | } 39 | -------------------------------------------------------------------------------- /dialectic/src/types/choose.rs: -------------------------------------------------------------------------------- 1 | use std::{any::Any, marker::PhantomData}; 2 | 3 | use super::sealed::IsSession; 4 | use super::*; 5 | 6 | use crate::tuple::{List, Tuple}; 7 | 8 | /// Actively [`choose`](crate::Chan::choose) between any of the protocols in the tuple 9 | /// `Choices`. 10 | /// 11 | /// At most 128 choices can be presented to a `Choose` type; to choose from more options, nest 12 | /// `Choose`s within each other. 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | pub struct Choose(PhantomData Choices>); 15 | 16 | impl Default for Choose { 17 | fn default() -> Self { 18 | Choose(PhantomData) 19 | } 20 | } 21 | 22 | impl IsSession for Choose {} 23 | 24 | impl HasDual for Choose 25 | where 26 | Choices: Tuple, 27 | Choices::AsList: EachHasDual, 28 | ::Duals: List + EachHasDual, 29 | { 30 | type DualSession = Offer<<::Duals as List>::AsTuple>; 31 | } 32 | 33 | impl Actionable for Choose { 34 | type NextAction = Self; 35 | } 36 | 37 | impl Scoped for Choose where 38 | Choices::AsList: EachScoped 39 | { 40 | } 41 | 42 | impl Subst for Choose 43 | where 44 | Choices: Tuple + 'static, 45 | Choices::AsList: EachSubst, 46 | >::Substituted: List, 47 | { 48 | type Substituted = Choose<<>::Substituted as List>::AsTuple>; 49 | } 50 | 51 | impl Then for Choose 52 | where 53 | Choices: Tuple + 'static, 54 | Choices::AsList: EachThen, 55 | >::Combined: List, 56 | { 57 | type Combined = Choose<<>::Combined as List>::AsTuple>; 58 | } 59 | 60 | impl Lift for Choose 61 | where 62 | Choices: Tuple + 'static, 63 | Choices::AsList: EachLift, 64 | >::Lifted: List, 65 | { 66 | type Lifted = Choose<<>::Lifted as List>::AsTuple>; 67 | } 68 | -------------------------------------------------------------------------------- /dialectic/src/types/continue.rs: -------------------------------------------------------------------------------- 1 | use super::sealed::IsSession; 2 | use super::*; 3 | use crate::unary::{self, Compare, Number, ToConstant, ToUnary}; 4 | 5 | /// Repeat a [`Loop`]. The type-level index points to the loop to be repeated, counted from the 6 | /// innermost starting at `0`. 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 8 | pub struct Continue(()); 9 | 10 | impl IsSession for Continue {} 11 | 12 | impl HasDual for Continue { 13 | type DualSession = Continue; 14 | } 15 | 16 | impl Scoped for Continue 17 | where 18 | Number: ToUnary, 19 | M: LessThan, 20 | { 21 | } 22 | 23 | impl Subst for Continue 24 | where 25 | Number: ToUnary, 26 | (N, M): Compare, P, Continue>, 27 | <(N, M) as Compare, P, Continue>>::Result: 'static, 28 | { 29 | type Substituted = <(N, M) as Compare, P, Continue>>::Result; 30 | } 31 | 32 | impl Then for Continue { 33 | type Combined = Continue; 34 | } 35 | 36 | impl Lift 37 | for Continue 38 | where 39 | Number: ToUnary, 40 | (M, N): unary::Add, 41 | P: ToConstant>, 42 | (M, Level): Compare, Continue, Continue>, 43 | { 44 | type Lifted = <(M, Level) as Compare, Continue, Continue>>::Result; 45 | } 46 | -------------------------------------------------------------------------------- /dialectic/src/types/done.rs: -------------------------------------------------------------------------------- 1 | use super::sealed::IsSession; 2 | use super::*; 3 | 4 | /// A finished session. The only thing to do with a [`Chan`](crate::Chan) when it is `Done` is to 5 | /// drop it or, preferably, [`close`](crate::Chan::close) it. 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 7 | pub struct Done(()); 8 | 9 | impl IsSession for Done {} 10 | 11 | impl HasDual for Done { 12 | type DualSession = Done; 13 | } 14 | 15 | impl Actionable for Done { 16 | type NextAction = Self; 17 | } 18 | 19 | impl Scoped for Done {} 20 | 21 | impl Subst for Done { 22 | type Substituted = Done; 23 | } 24 | 25 | impl Then for Done 26 | where 27 | P: Lift, 28 | { 29 | type Combined = P::Lifted; 30 | } 31 | 32 | impl Lift for Done { 33 | type Lifted = Done; 34 | } 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | #![allow(unused)] 39 | 40 | /// Tests whether [`Done`] is actually [`Done`] when inside the first argument to [`Call`]. 41 | #[test] 42 | fn done_in_seq() { 43 | use crate::prelude::*; 44 | use crate::types::*; 45 | 46 | type S = Session! { 47 | loop { 48 | call { 49 | send String; 50 | } 51 | recv String; 52 | break; 53 | } 54 | }; 55 | 56 | async fn serve(chan: Chan) -> Result<(), Box> 57 | where 58 | Tx: std::marker::Send + Transmitter + Transmit, 59 | Rx: std::marker::Send + Receive, 60 | Tx::Error: std::error::Error, 61 | Rx::Error: std::error::Error, 62 | { 63 | let chan = chan 64 | .call(|chan| async move { 65 | chan.send("Hello!".to_string()).await?.close(); 66 | Ok::<_, Box>(()) 67 | }) 68 | .await? 69 | .1 70 | .unwrap(); 71 | let (_string, chan) = chan.recv().await?; 72 | chan.close(); 73 | Ok(()) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /dialectic/src/types/loop.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use super::sealed::IsSession; 4 | use super::*; 5 | 6 | /// Label a loop point, which can be reiterated with [`Continue`]. 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | pub struct Loop

(PhantomData P>); 9 | 10 | impl

Default for Loop

{ 11 | fn default() -> Self { 12 | Loop(PhantomData) 13 | } 14 | } 15 | 16 | impl IsSession for Loop

{} 17 | 18 | impl

HasDual for Loop

19 | where 20 | P: HasDual, 21 | { 22 | type DualSession = Loop<

::DualSession>; 23 | } 24 | 25 | impl>> Scoped for Loop

{} 26 | 27 | impl

Actionable for Loop

28 | where 29 | P: Subst, Z>, 30 | P::Substituted: Actionable, 31 | { 32 | type NextAction = ::NextAction; 33 | } 34 | 35 | impl Subst for Loop

36 | where 37 | P: Subst>, 38 | { 39 | type Substituted = Loop<

>>::Substituted>; 40 | } 41 | 42 | impl Then for Loop

43 | where 44 | P: Then>, 45 | { 46 | type Combined = Loop<

>>::Combined>; 47 | } 48 | 49 | impl Lift for Loop

50 | where 51 | P: Lift>, 52 | { 53 | type Lifted = Loop<

>>::Lifted>; 54 | } 55 | -------------------------------------------------------------------------------- /dialectic/src/types/offer.rs: -------------------------------------------------------------------------------- 1 | use std::{any::Any, marker::PhantomData}; 2 | 3 | use super::sealed::IsSession; 4 | use super::*; 5 | 6 | use crate::tuple::{List, Tuple}; 7 | 8 | /// Passively [`offer!`](crate::offer) a choice between any of the protocols in the tuple `Choices`. 9 | /// 10 | /// At most 128 choices can be offered in a single `Offer` type; to supply more options, nest 11 | /// `Offer`s within each other. 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 13 | pub struct Offer(PhantomData Choices>); 14 | 15 | impl Default for Offer { 16 | fn default() -> Self { 17 | Offer(PhantomData) 18 | } 19 | } 20 | 21 | impl IsSession for Offer {} 22 | 23 | impl HasDual for Offer 24 | where 25 | Choices: Tuple, 26 | Choices::AsList: EachHasDual, 27 | ::Duals: List + EachHasDual, 28 | { 29 | type DualSession = Choose<<::Duals as List>::AsTuple>; 30 | } 31 | 32 | impl Actionable for Offer { 33 | type NextAction = Self; 34 | } 35 | 36 | impl Scoped for Offer where 37 | Choices::AsList: EachScoped 38 | { 39 | } 40 | 41 | impl Subst for Offer 42 | where 43 | Choices: Tuple + 'static, 44 | Choices::AsList: EachSubst, 45 | >::Substituted: List, 46 | { 47 | type Substituted = Offer<<>::Substituted as List>::AsTuple>; 48 | } 49 | 50 | impl Then for Offer 51 | where 52 | Choices: Tuple + 'static, 53 | Choices::AsList: EachThen, 54 | >::Combined: List, 55 | { 56 | type Combined = Offer<<>::Combined as List>::AsTuple>; 57 | } 58 | 59 | impl Lift for Offer 60 | where 61 | Choices: Tuple + 'static, 62 | Choices::AsList: EachLift, 63 | >::Lifted: List, 64 | { 65 | type Lifted = Offer<<>::Lifted as List>::AsTuple>; 66 | } 67 | -------------------------------------------------------------------------------- /dialectic/src/types/recv.rs: -------------------------------------------------------------------------------- 1 | use super::sealed::IsSession; 2 | use crate::types::*; 3 | use std::{any::Any, marker::PhantomData}; 4 | 5 | /// Receive a message of type `T` using [`recv`](crate::Chan::recv), then continue with 6 | /// protocol `P`. 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | pub struct Recv(PhantomData T>, PhantomData P>); 9 | 10 | impl Default for Recv { 11 | fn default() -> Self { 12 | Recv(PhantomData, PhantomData) 13 | } 14 | } 15 | 16 | impl IsSession for Recv {} 17 | 18 | impl HasDual for Recv { 19 | type DualSession = Send; 20 | } 21 | 22 | impl Actionable for Recv { 23 | type NextAction = Self; 24 | } 25 | 26 | impl> Scoped for Recv {} 27 | 28 | impl, Q> Subst for Recv { 29 | type Substituted = Recv; 30 | } 31 | 32 | impl, Q> Then for Recv { 33 | type Combined = Recv; 34 | } 35 | 36 | impl, Level: Unary> Lift for Recv { 37 | type Lifted = Recv; 38 | } 39 | -------------------------------------------------------------------------------- /dialectic/src/types/send.rs: -------------------------------------------------------------------------------- 1 | use super::sealed::IsSession; 2 | use crate::types::*; 3 | use std::{any::Any, marker::PhantomData}; 4 | 5 | /// Send a message of type `T` using [`send`](crate::Chan::send), then continue with 6 | /// protocol `P`. 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | pub struct Send(PhantomData T>, PhantomData P>); 9 | 10 | impl Default for Send { 11 | fn default() -> Self { 12 | Send(PhantomData, PhantomData) 13 | } 14 | } 15 | 16 | impl IsSession for Send {} 17 | 18 | impl HasDual for Send { 19 | type DualSession = Recv; 20 | } 21 | 22 | impl Actionable for Send { 23 | type NextAction = Self; 24 | } 25 | 26 | impl> Scoped for Send {} 27 | 28 | impl, Q> Subst for Send { 29 | type Substituted = Send; 30 | } 31 | 32 | impl, Q> Then for Send { 33 | type Combined = Send; 34 | } 35 | 36 | impl, Level: Unary> Lift for Send { 37 | type Lifted = Send; 38 | } 39 | -------------------------------------------------------------------------------- /dialectic/src/types/split.rs: -------------------------------------------------------------------------------- 1 | use std::{any::Any, marker::PhantomData}; 2 | 3 | use super::sealed::IsSession; 4 | use super::*; 5 | 6 | /// Split the connection into send-only and receive-only halves using [`split`](crate::Chan::split). 7 | /// 8 | /// The type `Split` means: do the [`Transmit`](crate::backend::Transmit)-only session `P` 9 | /// concurrently with the [`Receive`](crate::backend::Receive)-only session `Q`, running them both 10 | /// to [`Done`], and when they've completed, do the session `R`. 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | pub struct Split( 13 | PhantomData P>, 14 | PhantomData Q>, 15 | PhantomData R>, 16 | ); 17 | 18 | impl Default for Split { 19 | fn default() -> Self { 20 | Split(PhantomData, PhantomData, PhantomData) 21 | } 22 | } 23 | 24 | impl IsSession for Split {} 25 | 26 | impl HasDual for Split { 27 | /// Note how the dual flips the position of `P` and `Q`, because `P::Dual` is a receiving 28 | /// session, and therefore belongs on the right of the split, and `Q::Dual` is a sending 29 | /// session, and therefore belongs on the left of the split. 30 | type DualSession = Split; 31 | } 32 | 33 | impl Actionable for Split { 34 | type NextAction = Self; 35 | } 36 | 37 | impl, Q: Scoped, R: Scoped> Scoped for Split {} 38 | 39 | impl, Q: Subst, R: Subst, S> Subst for Split { 40 | type Substituted = Split; 41 | } 42 | 43 | impl, S> Then for Split { 44 | type Combined = Split; 45 | } 46 | 47 | impl, Q: Lift, R: Lift> Lift 48 | for Split 49 | { 50 | type Lifted = Split; 51 | } 52 | -------------------------------------------------------------------------------- /dialectic/src/unary.rs: -------------------------------------------------------------------------------- 1 | //! The unary numbers, represented by zero [`Z`] and successor [`S`]. 2 | 3 | /// The number zero. 4 | /// 5 | /// # Examples 6 | /// 7 | /// ``` 8 | /// use dialectic::unary::Z; 9 | /// 10 | /// let zero: Z = Z; 11 | /// ``` 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 13 | pub struct Z; 14 | 15 | /// The successor of `N` (i.e. `N + 1`). 16 | /// 17 | /// # Examples 18 | /// 19 | /// ``` 20 | /// use dialectic::unary::{S, Z}; 21 | /// 22 | /// let one: S = S(Z); 23 | /// ``` 24 | #[repr(transparent)] 25 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 26 | pub struct S(pub N); 27 | 28 | /// A convenient type synonym for writing out unary types using constants. 29 | pub type UnaryOf = as ToUnary>::AsUnary; 30 | 31 | /// All unary numbers can be converted to their value-level equivalent `usize`. 32 | /// 33 | /// # Examples 34 | /// 35 | /// ``` 36 | /// # #![recursion_limit = "256"] 37 | /// use dialectic::prelude::*; 38 | /// use dialectic::unary::*; 39 | /// 40 | /// assert_eq!(>::VALUE, 0); 41 | /// assert_eq!(>::VALUE, 1); 42 | /// assert_eq!(>::VALUE, 2); 43 | /// // ... 44 | /// assert_eq!(>::VALUE, 256); 45 | /// ``` 46 | pub trait Unary: sealed::Unary + Sized + Sync + Send + 'static { 47 | /// The runtime value of this type-level number, as a `usize`. 48 | const VALUE: usize; 49 | } 50 | 51 | impl Unary for Z { 52 | const VALUE: usize = 0; 53 | } 54 | 55 | impl Unary for S { 56 | const VALUE: usize = N::VALUE + 1; 57 | } 58 | 59 | /// Ensure than a unary number is strictly less than some other number. 60 | /// 61 | /// # Examples 62 | /// 63 | /// This compiles, because `1 < 2`: 64 | /// 65 | /// ``` 66 | /// use dialectic::prelude::*; 67 | /// use dialectic::unary::*; 68 | /// 69 | /// fn ok() where UnaryOf<1>: LessThan> {} 70 | /// ``` 71 | /// 72 | /// But this does not compile, because `2 >= 1`: 73 | /// 74 | /// ```compile_fail 75 | /// # use dialectic::prelude::*; 76 | /// # use dialectic::unary::*; 77 | /// # 78 | /// fn bad() where UnaryOf<2>: LessThan> {} 79 | /// ``` 80 | /// 81 | /// Because [`LessThan`] is a *strict* less-than relationship (i.e. `<`, not `<=`), this does not 82 | /// compile either: 83 | /// 84 | /// ```compile_fail 85 | /// # use dialectic::prelude::*; 86 | /// # use dialectic::unary::*; 87 | /// # 88 | /// fn bad() where UnaryOf<100>: LessThan> {} 89 | /// ``` 90 | pub trait LessThan 91 | where 92 | Self: Unary, 93 | { 94 | } 95 | 96 | impl LessThan> for Z {} 97 | 98 | impl> LessThan> for S {} 99 | 100 | /// Compare two unary numbers and branch on their comparison, at the type level. 101 | /// 102 | /// # Examples 103 | /// 104 | /// ``` 105 | /// use dialectic::prelude::*; 106 | /// use dialectic::unary::{Compare, UnaryOf}; 107 | /// use static_assertions::assert_type_eq_all; 108 | /// 109 | /// assert_type_eq_all!(<(UnaryOf<0>, UnaryOf<1>) as Compare>::Result, u8); 110 | /// assert_type_eq_all!(<(UnaryOf<1>, UnaryOf<1>) as Compare>::Result, u16); 111 | /// assert_type_eq_all!(<(UnaryOf<2>, UnaryOf<1>) as Compare>::Result, u32); 112 | /// ``` 113 | pub trait Compare: sealed::Compare { 114 | /// The result of the comparison: either `T` if `Self == N` or `E` if `Self != N`. 115 | type Result; 116 | } 117 | 118 | impl Compare 119 | for (S, S) 120 | where 121 | (N, M): Compare, 122 | { 123 | type Result = <(N, M) as Compare>::Result; 124 | } 125 | 126 | impl Compare for (Z, Z) { 127 | type Result = IfEqual; 128 | } 129 | 130 | impl Compare for (S, Z) { 131 | type Result = IfGreater; 132 | } 133 | 134 | impl Compare for (Z, S) { 135 | type Result = IfLess; 136 | } 137 | 138 | /// Add two unary numbers at the type level. 139 | /// 140 | /// # Examples 141 | /// 142 | /// ``` 143 | /// use dialectic::prelude::*; 144 | /// use dialectic::unary::*; 145 | /// use static_assertions::assert_type_eq_all; 146 | /// 147 | /// assert_type_eq_all!(<(UnaryOf<1>, UnaryOf<1>) as Add>::Result, UnaryOf<2>); 148 | /// assert_type_eq_all!(<(UnaryOf<5>, UnaryOf<7>) as Add>::Result, UnaryOf<12>); 149 | /// ``` 150 | pub trait Add: sealed::Add { 151 | /// The result of the addition. 152 | type Result: Unary; 153 | } 154 | 155 | impl Add for (N, Z) { 156 | type Result = N; 157 | } 158 | 159 | impl Add for (N, S) 160 | where 161 | (N, M): Add, 162 | { 163 | type Result = S<<(N, M) as Add>::Result>; 164 | } 165 | 166 | /// A trait marking wrapped type-level constants. 167 | pub trait Constant: sealed::Constant {} 168 | 169 | /// A wrapper for type-level `usize` values to allow implementing traits on them. 170 | #[derive(Debug)] 171 | pub enum Number {} 172 | 173 | impl Constant for Number {} 174 | 175 | /// A trait which allows conversion from a wrapper type over a type-level `usize` to a unary 176 | /// type-level number representation. 177 | pub trait ToUnary { 178 | /// The result of conversion. 179 | type AsUnary: Unary + ToConstant; 180 | } 181 | 182 | /// A trait which allows conversion from a unary type-level representation to a wrapper over a 183 | /// type-level `usize`. 184 | pub trait ToConstant: Unary { 185 | /// The result of conversion. 186 | type AsConstant: Constant + ToUnary; 187 | } 188 | 189 | dialectic_macro::generate_unary_conversion_impls!(256); 190 | 191 | mod sealed { 192 | use super::*; 193 | pub trait Unary: 'static {} 194 | impl Unary for Z {} 195 | impl Unary for S {} 196 | 197 | pub trait Constant: 'static {} 198 | impl Constant for Number {} 199 | 200 | pub trait Compare {} 201 | impl Compare for (N, M) {} 202 | 203 | pub trait Add {} 204 | impl Add for (N, M) {} 205 | } 206 | -------------------------------------------------------------------------------- /readme.py: -------------------------------------------------------------------------------- 1 | #! /bin/env python3 2 | 3 | # This file automatically generates the README.md file for this repository based upon the first 4 | # section of the crate documentation. 5 | 6 | main_readme_links = """ 7 | [`dialectic-tokio-mpsc`]: https://crates.io/crates/dialectic-tokio-mpsc 8 | [`dialectic-tokio-serde`]: https://crates.io/crates/dialectic-tokio-serde 9 | [`dialectic-tokio-serde-bincode`]: https://crates.io/crates/dialectic-tokio-serde-bincode 10 | [`dialectic-tokio-serde-json`]: https://crates.io/crates/dialectic-tokio-serde-json 11 | [`bincode`]: https://crates.io/crates/bincode 12 | [`serde_json`]: https://crates.io/crates/serde_json 13 | [tutorial-style tour of the crate]: https://docs.rs/dialectic/latest/dialectic/tutorial/index.html 14 | [quick reference]: https://docs.rs/dialectic/latest/dialectic/#quick-reference 15 | [reference documentation]: https://docs.rs/dialectic 16 | [`types`]: https://docs.rs/dialectic/latest/dialectic/types/index.html 17 | [`Chan`]: https://docs.rs/dialectic/latest/dialectic/struct.Chan.html 18 | [`Transmit`]: https://docs.rs/dialectic/latest/dialectic/backend/trait.Transmit.html 19 | [`Receive`]: https://docs.rs/dialectic/latest/dialectic/backend/trait.Receive.html 20 | [`backend`]: https://docs.rs/dialectic/latest/dialectic/backend/index.html 21 | [`Session!`]: https://docs.rs/dialectic/latest/dialectic/macro.Session.html 22 | """ 23 | 24 | with open("README.md", "w") as readme: 25 | readme.write("# Dialectic\n") 26 | with open("dialectic/src/lib.rs", "r") as lib: 27 | for line in lib: 28 | 29 | if line.startswith("/*!"): 30 | line = line[3:] 31 | 32 | if line.endswith("\n"): 33 | break 34 | 35 | readme.write(line) 36 | readme.write(main_readme_links) 37 | 38 | with open("dialectic-compiler/README.md", "w") as readme: 39 | readme.write("# Dialectic session type macro compiler\n") 40 | with open("dialectic-compiler/src/lib.rs", "r") as lib: 41 | for line in lib: 42 | if line.startswith("/*!"): 43 | line = line[3:] 44 | 45 | if line.startswith("```text"): 46 | line = line.replace("```text", "```rust") 47 | 48 | if line.endswith("\n"): 49 | break 50 | 51 | readme.write(line) 52 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | stable -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boltlabs-inc/dialectic/0d545aceb4a204b4d412e8fcf877ff2a41e35c3b/rustfmt.toml --------------------------------------------------------------------------------