├── .gitignore ├── src ├── test │ └── mod.rs ├── proof │ ├── mod.rs │ └── equal.rs ├── named │ ├── mod.rs │ └── internal.rs ├── lib.rs └── macros.rs ├── rust-toolchain.toml ├── rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── docs ├── proof_macro.md ├── exists_macro.md ├── demo.md └── implementation.md ├── examples ├── number.rs ├── list.rs ├── data_structures.rs └── access_control.rs └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /src/test/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/proof/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod equal; 2 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | -------------------------------------------------------------------------------- /src/named/mod.rs: -------------------------------------------------------------------------------- 1 | mod internal; 2 | 3 | pub use internal::{ 4 | with_seed, 5 | HasType, 6 | Life, 7 | Name, 8 | Named, 9 | Seed, 10 | }; 11 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | max_width = 80 3 | use_field_init_shorthand = true 4 | edition = "2018" 5 | reorder_modules = true 6 | brace_style = "AlwaysNextLine" 7 | fn_args_layout = "Vertical" 8 | imports_layout = "Vertical" 9 | imports_granularity = "Crate" 10 | group_imports = "StdExternalCrate" 11 | fn_single_line = false 12 | empty_item_single_line = true 13 | format_code_in_doc_comments = true 14 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "mononym" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "paste", 10 | ] 11 | 12 | [[package]] 13 | name = "paste" 14 | version = "1.0.5" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mononym" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "Type-level named values with partial dependent type support in Rust" 6 | homepage = "https://github.com/maybevoid/mononym" 7 | repository = "https://github.com/maybevoid/mononym" 8 | authors = [ "Soares Chen " ] 9 | license = "MIT OR Apache-2.0" 10 | keywords = [ "dependent-types" ] 11 | readme = "./README.md" 12 | 13 | [dependencies] 14 | paste = "1.0.5" 15 | -------------------------------------------------------------------------------- /src/proof/equal.rs: -------------------------------------------------------------------------------- 1 | use crate::named::*; 2 | 3 | crate::proof! { 4 | IsEqual(first: T, second: T); 5 | } 6 | 7 | pub fn check_equal, SecondVal: HasType>( 8 | first: &Named, 9 | second: &Named, 10 | ) -> Option> 11 | { 12 | if first.value() == second.value() { 13 | Some(IsEqual::new()) 14 | } else { 15 | None 16 | } 17 | } 18 | 19 | pub fn equal_commutative, SecondVal: HasType>( 20 | _is_equal: IsEqual 21 | ) -> IsEqual 22 | { 23 | IsEqual::new() 24 | } 25 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(generic_associated_types)] 3 | 4 | /*! 5 | Mononym is a library for creating unique type-level names for each value 6 | in Rust. The core type `Named` represents a named value of type 7 | `T` with a unique type `Name` as its name. Mononym guarantees that there 8 | can be no two values with the same name. With that, the `Name` type 9 | serves as a unique representation of a Rust value at the type level. 10 | 11 | Mononym enables the use of the design pattern 12 | [Ghosts of Departed Proofs](https://kataskeue.com/gdp.pdf) in Rust. 13 | It provides macros that simplify the definition of 14 | [dependent pairs](https://docs.idris-lang.org/en/latest/tutorial/typesfuns.html#dependent-pairs) 15 | and proof objects in Rust. Although there is still limited support for 16 | a full dependently-typed programming in Rust, Mononym helps us move a 17 | small step toward that direction by making it possible to refer to 18 | values in types. 19 | 20 | See [`docs::demo`] for an example demo for using Mononym, and see 21 | [`docs::implementation`] for how `mononym` implements unique name 22 | generation in Rust. 23 | */ 24 | 25 | /** 26 | The main implementation for named data types. 27 | */ 28 | pub mod named; 29 | 30 | pub mod proof; 31 | 32 | #[doc(hidden)] 33 | pub mod macros; 34 | 35 | pub use named::{ 36 | with_seed, 37 | HasType, 38 | Life, 39 | Name, 40 | Named, 41 | Seed, 42 | }; 43 | 44 | #[cfg(doc)] 45 | pub mod docs 46 | { 47 | /*! 48 | This is a rustdoc-only module providing in depth documentation 49 | on the library. 50 | */ 51 | 52 | #[doc = include_str!("../docs/implementation.md")] 53 | pub mod implementation 54 | {} 55 | 56 | #[doc = include_str!("../docs/demo.md")] 57 | pub mod demo 58 | {} 59 | } 60 | 61 | #[cfg(test)] 62 | extern crate alloc; 63 | 64 | #[cfg(test)] 65 | mod test; 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mononym 2 | 3 | [![Crates.io][crates-badge]][crates-url] 4 | [![Documentation][doc-badge]][doc-url] 5 | [![Apache licensed][license-badge]][license-url] 6 | 7 | [crates-badge]: https://img.shields.io/crates/v/mononym.svg 8 | [crates-url]: https://crates.io/crates/mononym 9 | [doc-badge]: https://docs.rs/mononym/badge.svg 10 | [doc-url]: https://docs.rs/mononym 11 | [license-badge]: https://img.shields.io/crates/l/mononym.svg 12 | [license-url]: https://github.com/maybevoid/mononym/blob/master/LICENSE 13 | [actions-badge]: https://github.com/maybevoid/mononym/workflows/Cargo%20Tests/badge.svg 14 | 15 | Mononym is a library for creating unique type-level names for each value in Rust. The core type `Named` represents a named value of type `T` with a unique type `Name` as its name. Mononym guarantees that there can be no two values with the same name. With that, the `Name` type serves as a unique representation of a Rust value at the type level. 16 | 17 | Mononym enables the use of the design pattern [Ghosts of Departed Proofs](https://kataskeue.com/gdp.pdf) in Rust. It provides macros that simplify the definition of [dependent pairs](https://docs.idris-lang.org/en/latest/tutorial/typesfuns.html#dependent-pairs) and proof objects in Rust. Although there is still limited support for a full dependently-typed programming in Rust, Mononym helps us move a small step toward that direction by making it possible to refer to values in types. 18 | 19 | ## Blog Posts 20 | 21 | - [Mononym: Type-Level Named Values in Rust - Part 1: Demo and Implementation](https://maybevoid.com/blog/mononym-part-1/) 22 | 23 | ## [Implementation Details](./docs/implementation.md) 24 | 25 | ## Examples 26 | 27 | Here are a few examples sneak peek that are currently work in progress. Apologize for the lack of documentation for the examples. There will be in-depth tutorials that go through the example code and guide the readers on how to use `mononym` to define proofs. 28 | 29 | - [Arithmetic](./examples/number.rs) 30 | - [Type-level Access Control](./examples/access_control.rs) 31 | - [Proofs on Data Structures](./examples/data_structures.rs) 32 | - [Extracting Information From List](./examples/list.rs) 33 | -------------------------------------------------------------------------------- /docs/proof_macro.md: -------------------------------------------------------------------------------- 1 | 2 | Macro to help define a new proof type. 3 | 4 | Suppose we want to write a module that tests whether a 5 | named `i64` is greater or equal to 0, and return a proof 6 | of it. We could define the proof type manually as follows: 7 | 8 | ```rust 9 | mod natural { 10 | use mononym::*; 11 | use core::marker::PhantomData; 12 | 13 | pub struct IsNatural>(PhantomData); 14 | 15 | impl > IsNatural { 16 | fn new() -> Self { 17 | Self(PhantomData) 18 | } 19 | } 20 | 21 | pub fn is_natural>( 22 | num: &Named, 23 | ) -> Option> 24 | { 25 | if *num.value() >= 0 { 26 | Some(IsNatural::new()) 27 | } else { 28 | None 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | We define `IsNatural` as a proof type for a named `i64` value 35 | with the name `NumVal` that shows that the number is greater 36 | or equal to 0. The type has a private 37 | [`PhantomData`](core::marker::PhantomData) body, as the 38 | existence of the proof value alone is sufficient. It provides 39 | a private `new()` method that allows functions to construct 40 | the proof object. 41 | 42 | The function `is_natural` then accepts a named number value 43 | with the name `NumVal`, and only constructs the 44 | proof `IsNatural` using `IsNatural::new()` 45 | if the condition is satisfied. 46 | 47 | As we define more proof types, the need to manually define 48 | the proof structs and methods can become tedious. The 49 | `proof!` macro simplifies the definition such as 50 | above so that we can write our code as follows: 51 | 52 | ```rust 53 | mod natural { 54 | use mononym::*; 55 | 56 | proof! { 57 | IsNatural(num: i64); 58 | } 59 | 60 | pub fn is_natural>( 61 | num: &Named, 62 | ) -> Option> 63 | { 64 | if *num.value() >= 0 { 65 | Some(IsNatural::new()) 66 | } else { 67 | None 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | Note that the new version of the `natural` module is the same as 74 | the original version. `proof!` takes care of generating 75 | the struct definition and the private `new` method, so that 76 | we do not need to keep repeating the same boilerplate definition. 77 | -------------------------------------------------------------------------------- /examples/number.rs: -------------------------------------------------------------------------------- 1 | pub mod less_than_eq 2 | { 3 | use mononym::*; 4 | 5 | proof! { 6 | LessThanEq(x: u32, y: u32); 7 | } 8 | 9 | pub fn check_less_than_eq, YVal: HasType>( 10 | x: &Named, 11 | y: &Named, 12 | ) -> Option> 13 | { 14 | if x.value() <= y.value() { 15 | Some(LessThanEq::new()) 16 | } else { 17 | None 18 | } 19 | } 20 | } 21 | 22 | pub mod non_zero 23 | { 24 | use mononym::*; 25 | 26 | proof! { 27 | NonZero(num: u32); 28 | } 29 | 30 | pub fn check_non_zero>( 31 | x: &Named 32 | ) -> Option> 33 | { 34 | if *x.value() != 0 { 35 | Some(NonZero::new()) 36 | } else { 37 | None 38 | } 39 | } 40 | } 41 | 42 | pub mod percentage 43 | { 44 | use mononym::*; 45 | 46 | use super::{ 47 | less_than_eq::LessThanEq, 48 | non_zero::NonZero, 49 | }; 50 | 51 | pub fn to_percentage< 52 | NumeratorVal: HasType, 53 | DenominatorVal: HasType, 54 | >( 55 | x: &Named, 56 | y: &Named, 57 | _numerator_lte_denom: &LessThanEq, 58 | _denom_not_zero: &NonZero, 59 | ) -> f64 60 | { 61 | let x: f64 = (*x.value()).into(); 62 | let y: f64 = (*y.value()).into(); 63 | x / y * 100.0 64 | } 65 | } 66 | 67 | fn main() 68 | { 69 | use less_than_eq::*; 70 | use mononym::*; 71 | use non_zero::*; 72 | use percentage::*; 73 | 74 | with_seed(|life| { 75 | let seed = life.into_seed(); 76 | let (seed1, seed2) = seed.replicate(); 77 | let x: Named<_, u32> = seed1.new_named(2); 78 | let y: Named<_, u32> = seed2.new_named(4); 79 | 80 | let x_is_less_than_y: LessThanEq<_, _> = 81 | check_less_than_eq(&x, &y).expect("should get proof that x <= y"); 82 | 83 | assert!(check_less_than_eq(&y, &x).is_none()); 84 | 85 | let y_not_zero = 86 | check_non_zero(&y).expect("should get proof that y is non zero"); 87 | 88 | let percent = to_percentage(&x, &y, &x_is_less_than_y, &y_not_zero); 89 | 90 | assert_eq!(percent, 50.0); 91 | 92 | println!("percentage of {}/{} is {}%", x.value(), y.value(), percent); 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | pub use paste::paste; 2 | 3 | #[doc = include_str!("../docs/exists_macro.md")] 4 | #[macro_export] 5 | macro_rules! exists { 6 | ( $( 7 | $exists:ident 8 | ( $name:ident : $type:ty ) => 9 | $proof:ident 10 | $( < $( $proof_param:ident ),+ $(,) ? > )? 11 | ( $( $suchthat:ident $( : $suchtype:ty )? ),* $(,)? ); 12 | )* 13 | 14 | ) => { 15 | $( 16 | $crate::exists_single! { 17 | $exists 18 | ( $name : $type ) => 19 | $proof 20 | $( < $( $proof_param ),* > )* 21 | ( $( $suchthat $( : $suchtype )* ),* ); 22 | } 23 | )* 24 | } 25 | } 26 | 27 | #[macro_export] 28 | macro_rules! exists_single { 29 | ( $exists:ident 30 | ( $name:ident : $type:ty ) => 31 | $proof:ident 32 | $( < $( $proof_param:ident ),+ $(,) ? > )? 33 | ( $( $suchthat:ident $( : $suchtype:ty )? ),* $(,)? ) 34 | $(;)? 35 | ) => { 36 | $crate::proof_single! { 37 | $proof 38 | $( < $( $proof_param ),* > )? 39 | ( $name : $type, $( $suchthat $( : $suchtype )? ),* ) 40 | } 41 | 42 | $crate::macros::paste! { 43 | pub struct [< $exists:camel >] 44 | < 45 | [< $name:camel Val >] : $crate::HasType<$type>, 46 | $( $( $proof_param, )* )? 47 | $( [< $suchthat:camel Val >] $( : $crate::HasType<$suchtype> )? ),* 48 | > 49 | { 50 | pub [< $name:snake >] : $crate::Named< 51 | [< $name:camel Val >], 52 | $type 53 | >, 54 | pub [< $proof:snake >] : 55 | [< $proof:camel >] 56 | < 57 | $( $( $proof_param, )* )? 58 | [< $name:camel Val >], 59 | $( [< $suchthat:camel Val >] ),* 60 | >, 61 | } 62 | 63 | fn [< new_ $exists:snake >] 64 | < 65 | $( $( $proof_param, )* )? 66 | $( [< $suchthat:camel Val >] $( : $crate::HasType<$suchtype> )? ),* 67 | > 68 | ( 69 | seed : impl $crate::Seed, 70 | [< $name:snake >] : $type, 71 | ) -> 72 | [< $exists:camel >] 73 | < impl $crate::HasType<$type>, 74 | $( $( $proof_param, )* )? 75 | $( [< $suchthat:camel Val >] ),* 76 | > 77 | { 78 | [< $exists:camel >] { 79 | [< $name:snake >]: $crate::Seed::new_named(seed, [< $name:snake >]), 80 | [< $proof:snake >] : [< $proof:camel >] ( ::core::marker::PhantomData ) 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | #[doc = include_str!("../docs/proof_macro.md")] 88 | #[macro_export] 89 | macro_rules! proof { 90 | ( $( 91 | $proof:ident 92 | $( < $( $proof_param:ident ),+ $(,) ? > )? 93 | ( $( $suchthat:ident $( : $suchtype:ty )? ),* $(,)? ); 94 | )+ 95 | ) => { 96 | $( 97 | $crate::proof_single! { 98 | $proof 99 | $( < $( $proof_param ),* > )? 100 | ( $( $suchthat $( : $suchtype )? ),* ); 101 | } 102 | )* 103 | } 104 | } 105 | 106 | #[macro_export] 107 | macro_rules! proof_single { 108 | ( $proof:ident 109 | $( < $( $proof_param:ident ),+ $(,) ? > )? 110 | ( $( $suchthat:ident $( : $suchtype:ty )? ),* $(,)? ) 111 | $(;)? 112 | ) => { 113 | $crate::macros::paste! { 114 | pub struct [< $proof:camel >] < 115 | $( $( $proof_param, )* )? 116 | $( [< $suchthat:camel Val >] $( : $crate::HasType<$suchtype> )? ),* 117 | > 118 | ( 119 | ::core::marker::PhantomData<( 120 | $( $( $proof_param, )* )? 121 | $( [< $suchthat:camel Val >] ),* 122 | )> 123 | ); 124 | 125 | impl 126 | < 127 | $( $( $proof_param, )* )? 128 | $( [< $suchthat:camel Val >] $( : $crate::HasType<$suchtype> )? ),* 129 | > 130 | [< $proof:camel >] 131 | < 132 | $( $( $proof_param, )* )? 133 | $( [< $suchthat:camel Val >] ),* 134 | > 135 | { 136 | fn new () -> Self 137 | { 138 | [< $proof:camel >] ( 139 | ::core::marker::PhantomData 140 | ) 141 | } 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /docs/exists_macro.md: -------------------------------------------------------------------------------- 1 | Macro to define new named value coupled with proofs 2 | in the form of dependent pairs. 3 | 4 | Suppose we have a named `u64` integer and we want to 5 | add 1 to the value using 6 | [checked addition](u64::checked_add) 7 | and return a new named `u64`. Along the way, 8 | we also want to construct a proof that shows that the new 9 | `u64` value is a _successor_ of the original value. 10 | 11 | We can define our successor proof similar to the other 12 | proofs that are defined by [`proof!`], but when 13 | we try to define our `add_one` function, we would 14 | hit a roadblock like follows: 15 | 16 | ```rust,ignore 17 | mod successor { 18 | use mononym::*; 19 | 20 | proof! { 21 | IsSuccessor(succ: u64, pred: u64); 22 | } 23 | 24 | pub fn add_one>( 25 | seed: impl Seed, 26 | num: &Named, 27 | ) -> Option<(Named, IsSuccessor)> 28 | { 29 | todo!() 30 | } 31 | } 32 | ``` 33 | 34 | The issue with the return type of our `add_one` function above is that 35 | the type `IsSuccessor` _depends_ on the fresh name type 36 | `impl Name` in `Named` to fill in the hole of `??`. 37 | This construct is known as a 38 | [_dependent pair_](https://docs.idris-lang.org/en/latest/tutorial/typesfuns.html#dependent-pairs) 39 | in languages with dependent types such as Idris. 40 | 41 | Although Rust do not have built in support for constructing dependent 42 | pairs, we can still emulate that by wrapping the two return types 43 | inside a new struct that we will call `ExistSuccessor`: 44 | 45 | ```rust 46 | mod successor { 47 | use mononym::*; 48 | use core::marker::PhantomData; 49 | 50 | proof! { 51 | IsSuccessor(succ: u64, pred: u64); 52 | } 53 | 54 | pub struct ExistSuccessor< 55 | SuccVal: HasType, 56 | PredVal: HasType, 57 | > { 58 | successor: Named, 59 | is_successor: IsSuccessor, 60 | } 61 | 62 | fn new_exist_successor< 63 | PredVal: HasType, 64 | > 65 | ( seed: impl Seed, 66 | succ: u64, 67 | ) -> ExistSuccessor, PredVal> 68 | { 69 | ExistSuccessor { 70 | successor: seed.new_named(succ), 71 | is_successor: IsSuccessor::new(), 72 | } 73 | } 74 | 75 | pub fn add_one>( 76 | seed: impl Seed, 77 | num: &Named, 78 | ) -> Option, NumVal>> 79 | { 80 | num.value() 81 | .checked_add(1) 82 | .map(|succ| new_exist_successor(seed, succ)) 83 | } 84 | } 85 | ``` 86 | 87 | The `ExistSuccessor` struct allows the same type parameter `SuccVal` 88 | to be used in both `Named` and `IsSuccessor`. Using that, we can 89 | redefine our `add_one` function to return the type 90 | `ExistSuccessor, NumVal>`, which the fresh 91 | name type `impl HasType` can be used in both places. 92 | 93 | Although the above solution works, there are quite a lot of 94 | boilerplate required to define just one dependent pair type. 95 | Therefore the `exists!` macro is provided so that the same 96 | definition above can be simplified into a single line definition 97 | as shown below: 98 | 99 | ```rust 100 | mod successor { 101 | use mononym::*; 102 | use core::marker::PhantomData; 103 | 104 | exists! { 105 | ExistSuccessor(succ: u64) => IsSuccessor(pred: u64); 106 | } 107 | 108 | pub fn add_one>( 109 | seed: impl Seed, 110 | num: &Named, 111 | ) -> Option, NumVal>> 112 | { 113 | num.value() 114 | .checked_add(1) 115 | .map(|succ| new_exist_successor(seed, succ)) 116 | } 117 | } 118 | ``` 119 | 120 | Note that the new version of the `successor` module is the same as 121 | the original version. The `exists!` takes care of generating 122 | the various struct definitions, as well as calling [`proof!`] 123 | to generate the `IsSuccessor` proof type. 124 | 125 | One limitation of the `exists!` macro is that the existential name 126 | is always in the first position of the following proof type. 127 | That is, the definition 128 | 129 | ``` 130 | # use mononym::*; 131 | exists! { ExistSuccessor(succ: u64) => IsSuccessor(pred: u64); } 132 | ``` 133 | 134 | leads to the call to 135 | 136 | ``` 137 | # use mononym::*; 138 | proof! { IsSuccessor(succ: u64, pred: u64); } 139 | ``` 140 | 141 | Therefore we cannot switch the position of the variables in 142 | the proof type, such as having 143 | 144 | ``` 145 | # use mononym::*; 146 | proof! { IsSuccessor(pred: u64, succ: u64); } 147 | ``` 148 | -------------------------------------------------------------------------------- /examples/list.rs: -------------------------------------------------------------------------------- 1 | mod list_size 2 | { 3 | use mononym::*; 4 | 5 | // Emulates the following dependent pair in Idris: 6 | // (t: Type) -> (list: Vec t) -> (size: usize ** ListHasSize size list) 7 | exists! { 8 | ExistSize(size: usize) => ListHasSize(list: Vec); 9 | } 10 | 11 | pub fn list_size>>( 12 | seed: impl Seed, 13 | list: &Named>, 14 | ) -> ExistSize, T, ListVal> 15 | { 16 | let size = list.value().len(); 17 | new_exist_size(seed, size) 18 | } 19 | } 20 | 21 | mod list_positive 22 | { 23 | use mononym::*; 24 | 25 | exists! { 26 | ExistPositives(count: usize) => ListHasPositives(list: Vec); 27 | } 28 | 29 | pub fn count_positive_integers>>( 30 | seed: impl Seed, 31 | list: &Named>, 32 | ) -> ExistPositives, ListVal> 33 | { 34 | let count = list.value().iter().filter(|x| **x > 0).count(); 35 | 36 | new_exist_positives(seed, count) 37 | } 38 | } 39 | 40 | mod greater_half 41 | { 42 | use mononym::*; 43 | 44 | proof! { 45 | GreaterThanHalf(numerator: usize, denominator: usize); 46 | } 47 | 48 | pub fn greater_than_half< 49 | NumeratorVal: HasType, 50 | DenominatorVal: HasType, 51 | >( 52 | x: &Named, 53 | y: &Named, 54 | ) -> Option> 55 | { 56 | if *x.value() * 2 > *y.value() { 57 | Some(GreaterThanHalf::new()) 58 | } else { 59 | None 60 | } 61 | } 62 | } 63 | 64 | mod greater_than_half_positive 65 | { 66 | use mononym::*; 67 | 68 | use super::{ 69 | greater_half::GreaterThanHalf, 70 | list_positive::ListHasPositives, 71 | list_size::ListHasSize, 72 | }; 73 | 74 | proof! { 75 | GreaterThanHalfPositive(list: Vec); 76 | } 77 | 78 | pub fn greater_than_half_positive< 79 | ListVal: HasType>, 80 | TotalCountVal: HasType, 81 | PositiveCountVal: HasType, 82 | >( 83 | _has_size: &ListHasSize, 84 | _has_positives: &ListHasPositives, 85 | _greater_than_half: &GreaterThanHalf, 86 | ) -> GreaterThanHalfPositive 87 | { 88 | GreaterThanHalfPositive::new() 89 | } 90 | } 91 | 92 | mod greater_than_half_positive_dynamic 93 | { 94 | use mononym::*; 95 | 96 | use super::{ 97 | greater_half::greater_than_half, 98 | greater_than_half_positive::{ 99 | greater_than_half_positive, 100 | GreaterThanHalfPositive, 101 | }, 102 | list_positive::count_positive_integers, 103 | list_size::list_size, 104 | }; 105 | 106 | pub fn maybe_greater_than_half_positive>>( 107 | seed: impl Seed, 108 | data: &Named>, 109 | ) -> Option> 110 | { 111 | let (seed1, seed2) = seed.replicate(); 112 | let size = list_size(seed1, data); 113 | let positives = count_positive_integers(seed2, data); 114 | 115 | let greater_half = greater_than_half(&positives.count, &size.size); 116 | 117 | greater_half.map(|greater_half| { 118 | greater_than_half_positive( 119 | &size.list_has_size, 120 | &positives.list_has_positives, 121 | &greater_half, 122 | ) 123 | }) 124 | } 125 | } 126 | 127 | mod process_static 128 | { 129 | use mononym::*; 130 | 131 | use super::greater_than_half_positive::GreaterThanHalfPositive; 132 | 133 | pub fn process_data>>( 134 | _: &Named>, 135 | _greater_than_half_positive: &GreaterThanHalfPositive, 136 | ) -> i64 137 | { 138 | 0 139 | } 140 | } 141 | 142 | mod process_data_dynamic 143 | { 144 | use mononym::*; 145 | 146 | use super::{ 147 | greater_than_half_positive_dynamic::maybe_greater_than_half_positive, 148 | process_static::process_data as process_data_static, 149 | }; 150 | 151 | #[derive(Debug, Eq, PartialEq)] 152 | pub enum Error 153 | { 154 | LessThanHalfPositive, 155 | } 156 | 157 | pub fn process_data(data: Vec) -> Result 158 | { 159 | with_seed(move |life| { 160 | let (seed1, seed2) = life.into_seed().replicate(); 161 | 162 | let data = seed1.new_named(data); 163 | 164 | let proof = maybe_greater_than_half_positive(seed2, &data) 165 | .ok_or(Error::LessThanHalfPositive)?; 166 | 167 | let res = process_data_static(&data, &proof); 168 | 169 | Ok(res) 170 | }) 171 | } 172 | } 173 | 174 | fn main() 175 | { 176 | use self::process_data_dynamic::{ 177 | process_data, 178 | Error, 179 | }; 180 | 181 | let data = vec![3, 2, 1, 0, -1]; 182 | 183 | process_data(data).unwrap(); 184 | 185 | let data = vec![3, 2, 1, 0, -1, -2]; 186 | 187 | let res = process_data(data); 188 | 189 | assert_eq!(res, Err(Error::LessThanHalfPositive)); 190 | } 191 | -------------------------------------------------------------------------------- /examples/data_structures.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | mod equal 4 | { 5 | use core::marker::PhantomData; 6 | 7 | pub struct Equal(PhantomData<(Val1, Val2)>); 8 | 9 | use mononym::*; 10 | 11 | pub fn check_equal, Val2: HasType>( 12 | value1: &Named, 13 | value2: &Named, 14 | ) -> Option> 15 | { 16 | if value1.value() == value2.value() { 17 | Some(Equal(PhantomData)) 18 | } else { 19 | None 20 | } 21 | } 22 | } 23 | 24 | mod size 25 | { 26 | use core::marker::PhantomData; 27 | 28 | use mononym::*; 29 | 30 | use super::sort::{ 31 | Sorted, 32 | SortedFrom, 33 | }; 34 | 35 | exists! { 36 | ExistListSize(size: usize) => ListHasSize(list: Vec); 37 | } 38 | 39 | proof! { 40 | NonEmpty(list: Vec); 41 | } 42 | 43 | // pub struct ListSize(PhantomData<(ListVal, SizeVal)>); 44 | 45 | // pub struct NonEmpty(PhantomData); 46 | 47 | // pub struct SizeResult< 48 | // Elem, 49 | // ListVal: HasType>, 50 | // SizeVal: HasType, 51 | // > { 52 | // size: Named, 53 | // size_proof: ListSize, 54 | // non_empty_proof: Option>, 55 | // phantom: PhantomData, 56 | // } 57 | 58 | pub fn list_size>>( 59 | seed: impl Seed, 60 | list: &Named>, 61 | ) -> ExistListSize, Elem, ListVal> 62 | { 63 | let size = list.value().len(); 64 | new_exist_list_size(seed, size) 65 | } 66 | 67 | pub fn list_not_empty< 68 | Elem, 69 | ListVal: HasType>, 70 | SizeVal: HasType, 71 | >( 72 | list_size: &Named, 73 | _list_has_size: &ListHasSize, 74 | ) -> Option> 75 | { 76 | if list_size.value() == &0 { 77 | None 78 | } else { 79 | Some(NonEmpty::new()) 80 | } 81 | } 82 | 83 | pub fn sorted_preserve_size< 84 | Elem, 85 | OldListVal: HasType>, 86 | NewListVal: HasType>, 87 | SizeVal: HasType, 88 | >( 89 | _size: ListHasSize, 90 | _sorted: Sorted, 91 | _sorted_from: SortedFrom, 92 | ) -> ListHasSize 93 | { 94 | ListHasSize::new() 95 | } 96 | 97 | pub fn sorted_preserve_non_empty< 98 | Elem, 99 | OldListVal: HasType>, 100 | NewListVal: HasType>, 101 | >( 102 | _non_empty: NonEmpty, 103 | _sorted: Sorted, 104 | _sorted_from: SortedFrom, 105 | ) -> NonEmpty 106 | { 107 | NonEmpty(PhantomData) 108 | } 109 | } 110 | 111 | mod sort 112 | { 113 | use core::marker::PhantomData; 114 | 115 | use mononym::{ 116 | HasType, 117 | Name, 118 | Named, 119 | Seed, 120 | }; 121 | 122 | pub struct Sorted(PhantomData); 123 | pub struct SortedFrom( 124 | PhantomData<(NewListVal, OldListVal)>, 125 | ); 126 | 127 | pub struct SortedResult< 128 | Elem, 129 | OldListVal: HasType>, 130 | NewListVal: HasType>, 131 | > { 132 | new_list: Named>, 133 | sorted: Sorted, 134 | sorted_from: SortedFrom, 135 | } 136 | 137 | pub fn sort<'a, Elem: 'a + Ord, ListVal: HasType>>( 138 | seed: impl Seed + 'a, 139 | list: Named>, 140 | ) -> SortedResult> + 'a> 141 | where 142 | { 143 | let mut new_list = list.into_value(); 144 | new_list.sort(); 145 | let new_list = seed.new_named(new_list); 146 | 147 | SortedResult { 148 | new_list, 149 | sorted: Sorted(PhantomData), 150 | sorted_from: SortedFrom(PhantomData), 151 | } 152 | } 153 | 154 | pub unsafe fn sorted_axiom() -> Sorted 155 | { 156 | Sorted(PhantomData) 157 | } 158 | 159 | pub unsafe fn sorted_from_axiom( 160 | ) -> SortedFrom 161 | { 162 | SortedFrom(PhantomData) 163 | } 164 | } 165 | 166 | mod min 167 | { 168 | use core::marker::PhantomData; 169 | 170 | use mononym::{ 171 | HasType, 172 | Name, 173 | Named, 174 | Seed, 175 | }; 176 | 177 | use super::{ 178 | size::NonEmpty, 179 | sort::Sorted, 180 | }; 181 | 182 | pub struct MinElem(PhantomData<(ListVal, ElemVal)>); 183 | 184 | pub struct MinResult<'a, Elem, ListVal, ElemVal: HasType<&'a Elem>> 185 | { 186 | elem: Named, 187 | min_proof: MinElem, 188 | } 189 | 190 | pub fn min>>( 191 | seed: impl Seed, 192 | list: &Named>, 193 | _sorted: Sorted, 194 | _non_empty: NonEmpty, 195 | ) -> MinResult> 196 | { 197 | let elem = list.value().first().unwrap(); 198 | 199 | MinResult { 200 | elem: seed.new_named(elem), 201 | min_proof: MinElem(PhantomData), 202 | } 203 | } 204 | } 205 | 206 | mod lookup 207 | { 208 | use core::marker::PhantomData; 209 | use std::collections::BTreeMap; 210 | 211 | use mononym::{ 212 | HasType, 213 | Name, 214 | Named, 215 | Seed, 216 | }; 217 | 218 | pub struct HasKey( 219 | PhantomData<(MapVal, KeyVal, ValueVal)>, 220 | ); 221 | 222 | pub struct LookupResult< 223 | 'a, 224 | Value, 225 | MapVal, 226 | KeyVal, 227 | ValueVal: HasType<&'a Value>, 228 | > { 229 | enry_value: Named, 230 | has_key_proof: HasKey, 231 | } 232 | 233 | pub fn lookup< 234 | 'a, 235 | Key, 236 | Value, 237 | MapVal: HasType>, 238 | KeyVal: HasType, 239 | >( 240 | seed: impl Seed, 241 | map: &'a Named>, 242 | key: &Named, 243 | ) -> Option>> 244 | where 245 | Key: Ord, 246 | Value: Clone, 247 | { 248 | map.value().get(key.value()).map(move |value| { 249 | let value = seed.new_named(value); 250 | 251 | LookupResult { 252 | enry_value: value, 253 | has_key_proof: HasKey(PhantomData), 254 | } 255 | }) 256 | } 257 | } 258 | 259 | fn main() {} 260 | -------------------------------------------------------------------------------- /src/named/internal.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | /** 4 | A marker trait that is used to represent unique type in Rust. 5 | `mononym` guarantees that any two `impl Name` generated by 6 | the library are always considered distinct types by Rust. 7 | 8 | This is mainly used as a type parameter inside types such as 9 | [`Seed`]. For example, the type `impl Seed` 10 | is used to represent a unique seed type with a fresh type 11 | `impl Name` being its name. 12 | */ 13 | pub trait Name: Send + Sync + Sealed {} 14 | 15 | /** 16 | A marker trait that is used to mark a type-level name being bound to 17 | a Rust value of the given type `T`. This helps ensure that functions 18 | that are generic over type-level names are "well-typed", with 19 | each name "having" their own type through `HasType`. 20 | 21 | With `Name` being a supertrait of `HasType`, this means that any 22 | name type can have their "type" erased by downcasting the type from 23 | `impl HasType` to `impl Name`. 24 | 25 | This trait is used as a type parameter inside [`Named`], so that the 26 | type `Named, T>` also attaches the type information 27 | to the type-level name associated with the named value. 28 | */ 29 | pub trait HasType: Name {} 30 | 31 | /** 32 | Represents a named value with a unique type-level name. `monoym` 33 | guarantees that there can never be two Rust values of the same 34 | type `Named`. With that, the name type `N` can be used to 35 | uniquely identify the underlying value at the type level. 36 | 37 | To ensure that functions that are generic over names are well-typed 38 | `Named` also requires the name type `N` to satisfy the trait bound 39 | [`HasType`]. Although this may introduce more boilerplate, 40 | it also helps programmers to always annotate the type of names 41 | when defining new generic functions. 42 | */ 43 | pub struct Named, T>(T, PhantomData); 44 | 45 | /** 46 | This trait is not exported so that the Name trait 47 | becomes a [_sealed trait_](https://rust-lang.github.io/api-guidelines/future-proofing.html) 48 | which user cannot provide custom implementation to. 49 | */ 50 | pub trait Sealed {} 51 | 52 | pub trait Seed: Sealed + Send + 'static 53 | { 54 | type Name: HasType; 55 | 56 | type Next1: Seed; 57 | 58 | type Next2: Seed; 59 | 60 | fn replicate(self) -> (Self::Next1, Self::Next2); 61 | 62 | fn new_named( 63 | self, 64 | value: T, 65 | ) -> Named, T>; 66 | } 67 | 68 | impl<'a> Life<'a> 69 | { 70 | /** 71 | Turns a unique lifetime proxy to an existential [`Seed`]. 72 | Since all `Life<'a>` must be unique via [`with_seed`], 73 | the returned `impl Seed` is also always unique. 74 | 75 | The existential associated types `Next1` and Next2` are 76 | also treated as different types by Rust, even though they 77 | all have the same underlying concrete type. In other words, 78 | the following test must fail: 79 | 80 | ```rust,compile_fail 81 | # use mononym::*; 82 | fn same(_: T, _: T) {} 83 | fn same_next<'a>(life: Life<'a>) { 84 | let (seed1, seed2) = life.into_seed().replicate(); 85 | same(seed1, seed2); 86 | } 87 | ``` 88 | */ 89 | pub fn into_seed(self) -> impl Seed 90 | { 91 | struct SomeName; 92 | struct SomeSeed; 93 | 94 | impl Sealed for SomeName {} 95 | impl Name for SomeName {} 96 | impl HasType for SomeName {} 97 | 98 | impl Sealed for SomeSeed {} 99 | impl Seed for SomeSeed 100 | { 101 | type Name = SomeName; 102 | type Next1 = SomeSeed; 103 | type Next2 = SomeSeed; 104 | 105 | fn replicate<'a>(self) -> (Self::Next1, Self::Next2) 106 | { 107 | (SomeSeed, SomeSeed) 108 | } 109 | 110 | fn new_named( 111 | self, 112 | value: T, 113 | ) -> Named, T> 114 | { 115 | Named(value, PhantomData) 116 | } 117 | } 118 | 119 | SomeSeed 120 | } 121 | } 122 | 123 | /** 124 | Turns a lifetime `'name` into a unique type `Life<'name>` 125 | with an invariant phantom lifetime. `Life` implements [`Name`] 126 | so that it can be turned into a unique `impl Name`. 127 | 128 | The body [`PhantomData`] has a phantom type `*mut &'name ()` 129 | to ensure that overlapping lifetimes such as 130 | `'name1: 'name2` are treated as distinct types and cannot be 131 | coerced into one another, unless they are exactly the same. 132 | 133 | For example, the following test should fail: 134 | 135 | ```rust,compile_fail 136 | # use mononym::*; 137 | fn same(_: T, _: T) {} 138 | fn same_life<'name1, 'name2: 'name1>( 139 | life1: Life<'name1>, 140 | life2: Life<'name2> 141 | ) { 142 | same(life1, life2); // error 143 | } 144 | ``` 145 | */ 146 | pub struct Life<'name>(PhantomData<*mut &'name ()>); 147 | 148 | /** 149 | Provides the continuation closure with a unique [`Seed`] with a unique lifetime 150 | `'name` and a unique name [`Life<'name>`](Life). 151 | 152 | This is achieved using 153 | [higher-ranked trait bounds](https://doc.rust-lang.org/nomicon/hrtb.html) 154 | by requiring the continuation closure to work for all lifetime `'name`. 155 | 156 | It is safe to have multiple nested calls to `with_seed`, as each call 157 | will generate new seed type with a unique `'name` lifetime. For example, 158 | the following code should fail to compile: 159 | 160 | ```rust,compile_fail 161 | # use mononym::*; 162 | fn same(_: T, _: T) {} 163 | with_seed(|seed1| { 164 | with_seed(|seed2| { 165 | same(seed1, seed2); // error 166 | same(seed1.new_named(1), seed2.new_named(1)); // error 167 | }); 168 | }); 169 | ``` 170 | 171 | The function allows the continuation closure to return any concrete type 172 | `R`, provided that the return type `R` does not depend on the provided 173 | `Seed` type in some way. This means that types such as [`Name`], 174 | [`Named`], and [`Seed`] cannot be used as a return value, as Rust 175 | consider that as allowing the lifetime `'name` to escape. For example, 176 | the following code should fail to compile: 177 | 178 | ```rust,compile_fail 179 | # use mononym::*; 180 | let res = with_seed(|seed| { seed.new_named(42).into_value() }); // ok 181 | let res = with_seed(|seed| { seed }); // error 182 | let res = with_seed(|seed| { seed.new_name() }); // error 183 | let res = with_seed(|seed| { seed.new_named(42) }); // error 184 | ``` 185 | */ 186 | pub fn with_seed(cont: impl for<'name> FnOnce(Life<'name>) -> R) -> R 187 | { 188 | cont(Life(PhantomData)) 189 | } 190 | 191 | impl, T> Named 192 | { 193 | /** 194 | Get a reference to the underlying value of the named value. 195 | `mononym` does not provide access to mutable reference to 196 | the underlying value, as mutation may invalidate the proofs 197 | of pre-conditions constructed from the original value. 198 | 199 | When using `Named`, it is up to the user to ensure that there 200 | is no accidental 201 | [interior mutability](https://doc.rust-lang.org/reference/interior-mutability.html) 202 | provided by the value type `T`. Otherwise, user must take 203 | into consideration of the possibility of interior mutability 204 | and ensure that the invariants assumed by the proofs defined 205 | cannot be violated. 206 | */ 207 | pub fn value(&self) -> &T 208 | { 209 | &self.0 210 | } 211 | 212 | /** 213 | Consume the named value and turn it back into the underlying value. 214 | After this, the underlying value is no longer associated with the 215 | type-level name, and can be safely mutated. 216 | 217 | Even though the named value is destroyed, the type-level name 218 | can still continue to present in other places such as proof objects. 219 | This can be useful for functions that only require proofs about 220 | a value, without requiring access to the value itself. 221 | */ 222 | pub fn into_value(self) -> T 223 | { 224 | self.0 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /docs/demo.md: -------------------------------------------------------------------------------- 1 | ## Demo 2 | 3 | As a quick demo, Mononym can be used to construct named values and 4 | create proofs that relate multiple modules as follows: 5 | 6 | ```rust 7 | pub mod less_than_eq 8 | { 9 | use mononym::*; 10 | 11 | proof! { 12 | LessThanEq(x: u32, y: u32); 13 | } 14 | 15 | pub fn check_less_than_eq, YVal: HasType>( 16 | x: &Named, 17 | y: &Named, 18 | ) -> Option> 19 | { 20 | if x.value() <= y.value() { 21 | Some(LessThanEq::new()) 22 | } else { 23 | None 24 | } 25 | } 26 | } 27 | 28 | use less_than_eq::*; 29 | use mononym::*; 30 | 31 | with_seed(|life| { 32 | let (seed1, seed2) = life.into_seed().replicate(); 33 | let x: Named<_, u32> = seed1.new_named(2); 34 | let y: Named<_, u32> = seed2.new_named(4); 35 | 36 | let x_is_less_than_y: LessThanEq<_, _> = 37 | check_less_than_eq(&x, &y).expect("should get proof that x <= y"); 38 | 39 | assert!(check_less_than_eq(&y, &x).is_none()); 40 | }); 41 | ``` 42 | 43 | In the first part of the program, we define a `less_than_eq` module that defines a `LessThanEq` proof type using the [`proof!`] macro provided by Mononym. The function `check_less_than_eq` is then defined to accept two _named values_ `x` and `y`, with the type names `XVal` and `YVal` representing the values `x` and `y` at the type level. In the function body, it checks that if `x` (`XVal`) is indeed less than or equal to `y` (`YVal`), it would return the proof in the form of `LessThanEq`. 44 | 45 | In the second part of the program, we start our test by calling the [`with_seed`](crate::with_seed) function with a continuation closure. The `with_seed` function generates a fresh name seed type which is then given to the closure as the `seed` variable. We then call `seed.replicate()` to create two new copies of seed, because it is an _affine_ value in Rust that can be used at most once. 46 | 47 | Each seed value can be used once to generate a named value. We then assign the variable `x` to `seed1.new_named(2)`, which has the type `Named<_, u32>`. Mononym provides the [`Named`](crate::Named) type to represent Rust values with unique name types. In this case, the `_` is used in the first position of the `Named` type for `x`, because the `new_named` method returns a `Named` type with an opaque type as the name. 48 | 49 | Similarly, the variable `y` is assigned to `seed2.new_named(4)`. Note that even though `y` also have the type `Named<_, u32>`, it is in fact a different type than the type of `x`. With Mononym guaranteeing the uniqueness of name types, we can conceptually refer to the name type of `x` being `XVal`, and the name type of `y` being `YVal` which is different from `XVal`. 50 | 51 | We call `check_less_than_eq(&x, &y)`, and expect the function to return a proof that `x` with a value of 2 is indeed less than `y` with a value of 4. Similarly, if we call `check_less_than_eq(&y, &x)`, we expect `None` to be returned and no proof should exist for 2 being greater than 4. 52 | 53 | Note that unlike in fully formalized languages like Coq, Mononym do not check that the proof `LessThan` can really only be constructed if and only if `x` <= `y`. It is up to the implementer of functions such as `check_less_than` to ensure that the construction of proofs match the underlying invariant. 54 | 55 | Nevertheless, proofs that are constructed using mononym are useful for encoding pre- and post-conditions for functions so that they can be composed in a declarative way. For example, we can define another `non_zero` module that produce proof that a number is non-zero, and together we can create a `percentage` module that converts the division of two numbers into percentage form if and only if x <= y and y is not zero: 56 | 57 | ```rust 58 | # mod example { 59 | # pub mod less_than_eq 60 | # { 61 | # use mononym::*; 62 | # 63 | # proof! { 64 | # LessThanEq(x: u32, y: u32); 65 | # } 66 | # 67 | # pub fn check_less_than_eq, YVal: HasType>( 68 | # x: &Named, 69 | # y: &Named, 70 | # ) -> Option> 71 | # { 72 | # if x.value() <= y.value() { 73 | # Some(LessThanEq::new()) 74 | # } else { 75 | # None 76 | # } 77 | # } 78 | # } 79 | # 80 | pub mod non_zero 81 | { 82 | use mononym::*; 83 | 84 | proof! { 85 | NonZero(num: u32); 86 | } 87 | 88 | pub fn check_non_zero>( 89 | x: &Named 90 | ) -> Option> 91 | { 92 | if *x.value() != 0 { 93 | Some(NonZero::new()) 94 | } else { 95 | None 96 | } 97 | } 98 | } 99 | 100 | pub mod percentage 101 | { 102 | use mononym::*; 103 | 104 | use super::{ 105 | less_than_eq::LessThanEq, 106 | non_zero::NonZero, 107 | }; 108 | 109 | pub fn to_percentage< 110 | NumeratorVal: HasType, 111 | DenominatorVal: HasType, 112 | >( 113 | x: &Named, 114 | y: &Named, 115 | _numerator_lte_denom: &LessThanEq, 116 | _denom_not_zero: &NonZero, 117 | ) -> f64 118 | { 119 | let x: f64 = (*x.value()).into(); 120 | let y: f64 = (*y.value()).into(); 121 | x / y * 100.0 122 | } 123 | } 124 | 125 | # } 126 | 127 | use example::*; 128 | use less_than_eq::*; 129 | use mononym::*; 130 | use non_zero::*; 131 | use percentage::*; 132 | 133 | with_seed(|life| { 134 | let (seed1, seed2) = life.into_seed().replicate(); 135 | let x: Named<_, u32> = seed1.new_named(2); 136 | let y: Named<_, u32> = seed2.new_named(4); 137 | 138 | let x_is_less_than_y: LessThanEq<_, _> = 139 | check_less_than_eq(&x, &y).expect("should get proof that x <= y"); 140 | 141 | let y_not_zero = 142 | check_non_zero(&y).expect("should get proof that y is non zero"); 143 | 144 | let percent = to_percentage(&x, &y, &x_is_less_than_y, &y_not_zero); 145 | 146 | println!("percentage of {}/{} is {}%", x.value(), y.value(), percent); 147 | }) 148 | ``` 149 | 150 | Similar to the `less_than_eq` module, the `check_non_zero` function in the `non_zero` module checks and may return a proof `NonZero` for a value being non-zero. 151 | 152 | The `percentage` module then defines the `to_percentage` function to make use of both the proofs of `LessThanEq` and `NonZero` before returning a `f64` percentage value. Assuming that the proofs in `less_than_eq` and `non_zero` are constructed properly, we can guarantee that the `f64` returned by `to_percentage` is always between 0 and 100. 153 | 154 | Using Mononym, the `to_percentage` function can be defined as a _total_ function, as in it does not need to return an `Option` or `Result` to handle the cases where either the numerator or denominator values are invalid. 155 | 156 | Although we cannot use Rust to formally prove that `to_percentage` will always return a valid percentage value between 0 and 100, Mononym can help reduce significantly the surface area of code that can potentially violate such invariant. 157 | 158 | Since we know that the proofs `LessThanEq` and `NonZero` can only be produced by `less_than_eq` and `non_zero`, we do not need to worry about any other potential cases that `to_percentage` can be given invalid arguments, no matter how large and how complex our code base become. 159 | 160 | We can also use Mononym together with techniques such as [property based testing](https://github.com/BurntSushi/quickcheck) to further ensure that the behavior of `to_percentage` is correct. In such property-based test, the random generator can attempt to randomly generate named values, and call `to_percentage` only when valid proofs can be constructed using `less_than_eq` and `non_zero`. This would significantly reduce the number of test cases needed, as compared to a brute force generator that have to test the function with the cartesian product of `u32` * `u32`. (Note that integration with testing framework is still a future work planned for Mononym) 161 | -------------------------------------------------------------------------------- /examples/access_control.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | mod data 4 | { 5 | #[derive(Debug, Clone, Eq, PartialEq)] 6 | pub struct UserId(pub String); 7 | 8 | #[derive(Debug, Clone, Eq, PartialEq)] 9 | pub struct PostId(pub String); 10 | 11 | #[derive(Debug, Clone, Eq, PartialEq)] 12 | pub struct GroupId(pub String); 13 | 14 | #[derive(Debug)] 15 | pub struct User 16 | { 17 | pub user_id: UserId, 18 | pub username: String, 19 | pub display_name: String, 20 | } 21 | 22 | #[derive(Debug)] 23 | pub struct Group 24 | { 25 | pub group_id: GroupId, 26 | pub group_name: String, 27 | pub description: String, 28 | } 29 | 30 | #[derive(Debug)] 31 | pub struct Post 32 | { 33 | pub post_id: PostId, 34 | pub author_id: UserId, 35 | pub group_id: Option, 36 | pub privacy: PostPrivacy, 37 | pub title: String, 38 | pub content: String, 39 | } 40 | 41 | #[derive(Debug)] 42 | pub enum PostPrivacy 43 | { 44 | Public, 45 | Private, 46 | GroupRead, 47 | GroupEdit, 48 | } 49 | } 50 | 51 | mod raw_query 52 | { 53 | use super::data::*; 54 | 55 | pub struct DbError; 56 | 57 | pub fn get_user_info(user_id: &UserId) -> Result 58 | { 59 | todo!() 60 | } 61 | 62 | pub fn get_user_groups(user_id: &UserId) -> Result, DbError> 63 | { 64 | todo!() 65 | } 66 | 67 | pub fn user_is_admin(user_id: &UserId) -> Result 68 | { 69 | todo!() 70 | } 71 | 72 | pub fn get_post_info(post_id: &PostId) -> Result 73 | { 74 | todo!() 75 | } 76 | } 77 | 78 | mod named_query 79 | { 80 | use mononym::*; 81 | 82 | use super::{ 83 | data::*, 84 | raw_query::{ 85 | self, 86 | DbError, 87 | }, 88 | }; 89 | 90 | exists! { 91 | ExistUser(user: User) => UserHasId(user_id: UserId); 92 | 93 | ExistGroups(groups: Vec) => UserInGroups(user_id: UserId); 94 | 95 | ExistPost(post: Post) => PostHasId(post_id: PostId); 96 | } 97 | 98 | proof! { 99 | UserIsAdmin(user_id: UserId); 100 | } 101 | 102 | pub fn get_user_info>( 103 | seed: impl Seed, 104 | user_id: Named, 105 | ) -> Result, UserIdVal>, DbError> 106 | { 107 | let user = raw_query::get_user_info(user_id.value())?; 108 | 109 | Ok(new_exist_user(seed, user)) 110 | } 111 | 112 | pub fn get_user_groups>( 113 | seed: impl Seed, 114 | user_id: &Named, 115 | ) -> Result>, UserIdVal>, DbError> 116 | { 117 | let groups = raw_query::get_user_groups(user_id.value())?; 118 | 119 | Ok(new_exist_groups(seed, groups)) 120 | } 121 | 122 | pub fn user_is_admin>( 123 | user_id: Named 124 | ) -> Result>, DbError> 125 | { 126 | let is_admin = raw_query::user_is_admin(user_id.value())?; 127 | 128 | if is_admin { 129 | Ok(Some(UserIsAdmin::new())) 130 | } else { 131 | Ok(None) 132 | } 133 | } 134 | 135 | pub fn get_post_info>( 136 | seed: impl Seed, 137 | post_id: &Named, 138 | ) -> Result, PostIdVal>, DbError> 139 | { 140 | let post = raw_query::get_post_info(post_id.value())?; 141 | 142 | Ok(new_exist_post(seed, post)) 143 | } 144 | } 145 | 146 | mod privacy 147 | { 148 | 149 | use mononym::*; 150 | 151 | use super::{ 152 | data::*, 153 | named_query::*, 154 | }; 155 | 156 | pub struct Public; 157 | pub struct Private; 158 | pub struct GroupRead; 159 | pub struct GroupEdit; 160 | 161 | proof! { 162 | PostHasPrivacy(post_id: PostId); 163 | } 164 | 165 | pub enum SomePostPrivacy> 166 | { 167 | Public(PostHasPrivacy), 168 | Private(PostHasPrivacy), 169 | GroupRead(PostHasPrivacy), 170 | GroupEdit(PostHasPrivacy), 171 | } 172 | 173 | pub fn check_post_privacy< 174 | UserIdVal: HasType, 175 | PostIdVal: HasType, 176 | PostVal: HasType, 177 | >( 178 | post: &Named, 179 | _post_has_id: &PostHasId, 180 | ) -> SomePostPrivacy 181 | { 182 | match post.value().privacy { 183 | PostPrivacy::Public => SomePostPrivacy::Public(PostHasPrivacy::new()), 184 | PostPrivacy::Private => SomePostPrivacy::Private(PostHasPrivacy::new()), 185 | PostPrivacy::GroupRead => { 186 | SomePostPrivacy::GroupRead(PostHasPrivacy::new()) 187 | } 188 | PostPrivacy::GroupEdit => { 189 | SomePostPrivacy::GroupEdit(PostHasPrivacy::new()) 190 | } 191 | } 192 | } 193 | } 194 | 195 | mod access_control 196 | { 197 | use mononym::*; 198 | 199 | use super::{ 200 | data::*, 201 | named_query::*, 202 | privacy::*, 203 | }; 204 | 205 | proof! { 206 | UserCanReadPost(post_id: PostId, user_id: UserId); 207 | 208 | UserCanEditPost(post_id: PostId, user_id: UserId); 209 | 210 | UserIsAuthor(post_id: PostId, user_id: UserId); 211 | 212 | UserInGroup(group_id: GroupId, user_id: UserId); 213 | } 214 | 215 | exists! { 216 | ExistPostGroup(group_id: GroupId) => PostInGroup(post_id: PostId); 217 | } 218 | 219 | pub fn check_user_is_author< 220 | UserIdVal: HasType, 221 | PostIdVal: HasType, 222 | PostVal: HasType, 223 | >( 224 | user_id: &Named, 225 | post: &Named, 226 | _post_has_id: &PostHasId, 227 | ) -> Option> 228 | { 229 | if &post.value().author_id == user_id.value() { 230 | Some(UserIsAuthor::new()) 231 | } else { 232 | None 233 | } 234 | } 235 | 236 | pub fn check_user_in_group< 237 | UserIdVal: HasType, 238 | GroupIdVal: HasType, 239 | GroupsVal: HasType>, 240 | >( 241 | user_id: &Named, 242 | group_id: &Named, 243 | groups: &Named>, 244 | _user_in_groups: &UserInGroups, 245 | ) -> Option> 246 | { 247 | for group in groups.value().iter() { 248 | if &group.group_id == group_id.value() { 249 | return Some(UserInGroup::new()); 250 | } 251 | } 252 | 253 | None 254 | } 255 | 256 | pub fn get_post_group, PostVal: HasType>( 257 | seed: impl Seed, 258 | post: &Named, 259 | _post_has_id: &PostHasId, 260 | ) -> Option, PostIdVal>> 261 | { 262 | post 263 | .value() 264 | .group_id 265 | .as_ref() 266 | .map(move |group_id| new_exist_post_group(seed, group_id.clone())) 267 | } 268 | 269 | pub fn check_post_in_group< 270 | GroupIdVal: HasType, 271 | PostIdVal: HasType, 272 | PostVal: HasType, 273 | >( 274 | group_id: &Named, 275 | post: &Named, 276 | _post_has_id: &PostHasId, 277 | ) -> Option> 278 | { 279 | if post.value().group_id.as_ref() == Some(group_id.value()) { 280 | Some(PostInGroup::new()) 281 | } else { 282 | None 283 | } 284 | } 285 | 286 | pub fn author_can_edit_post< 287 | UserIdVal: HasType, 288 | PostIdVal: HasType, 289 | >( 290 | _user_is_author: &UserIsAuthor 291 | ) -> UserCanEditPost 292 | { 293 | UserCanEditPost::new() 294 | } 295 | 296 | pub fn can_edit_also_can_read< 297 | UserIdVal: HasType, 298 | PostIdVal: HasType, 299 | >( 300 | _can_edit: &UserCanEditPost 301 | ) -> UserCanReadPost 302 | { 303 | UserCanReadPost::new() 304 | } 305 | 306 | pub fn anyone_can_read_public_post< 307 | UserIdVal: HasType, 308 | PostIdVal: HasType, 309 | >( 310 | _post_is_public: &PostHasPrivacy 311 | ) -> UserCanReadPost 312 | { 313 | UserCanReadPost::new() 314 | } 315 | 316 | pub fn admin_can_edit_any_post< 317 | UserIdVal: HasType, 318 | PostIdVal: HasType, 319 | >( 320 | _user_is_admin: &UserIsAdmin 321 | ) -> UserCanEditPost 322 | { 323 | UserCanEditPost::new() 324 | } 325 | pub fn group_member_can_read_post_with_group_read_privacy< 326 | UserIdVal: HasType, 327 | PostIdVal: HasType, 328 | GroupIdVal: HasType, 329 | >( 330 | _user_in_group: &UserInGroup, 331 | _post_in_group: &PostInGroup, 332 | _post_has_group_read_privacy: &PostHasPrivacy, 333 | ) -> UserCanReadPost 334 | { 335 | UserCanReadPost::new() 336 | } 337 | 338 | pub fn group_member_can_edit_post_with_group_edit_privacy< 339 | UserIdVal: HasType, 340 | PostIdVal: HasType, 341 | GroupIdVal: HasType, 342 | >( 343 | _user_in_group: &UserInGroup, 344 | _post_in_group: &PostInGroup, 345 | _post_has_group_read_privacy: &PostHasPrivacy, 346 | ) -> UserCanEditPost 347 | { 348 | UserCanEditPost::new() 349 | } 350 | } 351 | 352 | fn main() {} 353 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Soares Ruofei Chen 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /docs/implementation.md: -------------------------------------------------------------------------------- 1 | 2 | # Implementation Details 3 | 4 | Mononym harness several features unique to Rust and have a simpler implementation of named values compared to existing implementations in languages like Haskell. 5 | 6 | ## `impl Trait` as Opaque Types 7 | 8 | At its core Mononym makes use of the idea that the use of `impl Trait` in return position produces a new [abstract type](https://doc.rust-lang.org/reference/types/impl-trait.html#abstract-return-types) that is unique to that function. 9 | 10 | Consider the following example: 11 | 12 | ```rust,compile_fail 13 | trait Name {} 14 | impl Name for () {} 15 | 16 | fn foo() -> impl Name {} 17 | fn bar() -> impl Name {} 18 | 19 | fn same(_: T, _: T) {} 20 | same(foo(), foo()); 21 | same(bar(), bar()); 22 | same(foo(), bar()); // error 23 | same(foo(), ()); // error 24 | same(bar(), ()); // error 25 | ``` 26 | 27 | We define a dummy trait `Name` that is implemented only for `()`. We then define two functions `foo()` and `bar()` that return `impl Name` by returning `()`. Although both `foo()` and `bar()` are in effect returning the same concrete type `()`, they are considered different types by the Rust compiler. 28 | 29 | To test whether the types of two values are equal, we define the function `same(_: T, _: T)` that pass the compilation check if two values have the same type. When we try to compile the code, we will find that both `same(foo(), foo())` and `same(bar(), bar())` pass the compilation, but `same(foo(), bar())` would result in a type error. Similarly, the check on `same(foo(), ())` fails, as Rust treats the returned `impl Name` different than the underlying type `()`. 30 | 31 | ## Generic-dependent Uniqueness of `impl Trait` 32 | 33 | The use of `impl Name` in return position provides the first step toward defining unique names for each value. However the above code shows an obvious issue of using returned `impl Name` as unique type, as the test `same(foo(), foo())` pass the compilation. This means that two calls to the same function returning `impl Trait` will return values of the same opaque type. If we want the name type to be truly unique, the test `same(foo(), foo())` should have failed. 34 | 35 | We can try to force the opaque type returned in a function returning `impl Name` unique by making the function _generic_: 36 | 37 | ```rust,compile_fail 38 | trait Name {} 39 | impl Name for () {} 40 | 41 | fn type_name(_: T) -> impl Name {} 42 | 43 | fn same(_: T, _: T) {} 44 | same(type_name(()), type_name(())); 45 | same(type_name(0_u64), type_name(0_u64)); 46 | same(type_name(()), type_name(0_u64)); // error 47 | same(type_name(0_u32), type_name(0_u64)); // error 48 | same(type_name(||{}), type_name(||{})) // error 49 | ``` 50 | 51 | In the above example, we define a `type_name` function that accepts a dummy value of any generic type, and return an `impl Name`. From there we can see that when `type_name` is called with the same type, the returned `impl Name` is considered the same type. As a result, the tests to `same(type_name(()), type_name(()))` and `same(type_name(0_u64), type_name(0_u64))` pass the compilation, but tests such as `same(type_name(()), type_name(0_u64))` fail the compilation. 52 | 53 | ## Closure Expressions as Unique Type 54 | 55 | In the last test, we also see that the compilation test for `same(type_name(||{}), type_name(||{}))` has failed. This is because each new closure expression in Rust produces a [unique anonymous type](https://doc.rust-lang.org/reference/types/closure.html). With this, we know that as long as we are providing different closure expressions, the returned `impl Name` would be considered different type by Rust. 56 | 57 | Moving one step further, we can instead define the implementation for `Name` using the anonymous closure type: 58 | 59 | ```rust,compile_fail 60 | use core::marker::PhantomData; 61 | 62 | trait Name {} 63 | struct SomeName(PhantomData); 64 | impl Name for SomeName {} 65 | 66 | fn unsafe_new_name(_: N) -> impl Name { 67 | SomeName::(PhantomData) 68 | } 69 | 70 | fn same(_: T, _: T) {} 71 | 72 | let f = ||{}; 73 | same(unsafe_new_name(f), unsafe_new_name(f)); 74 | 75 | fn foo() -> impl Name { 76 | unsafe_new_name(||{}) 77 | } 78 | same(foo(), foo()) 79 | 80 | same(unsafe_new_name(||{}), unsafe_new_name(||{})); // error 81 | ``` 82 | 83 | We define a struct `SomeName` that implements `Name` based on the unique type `N`. We then rename our name generator function to `unsafe_new_name` and make it return `SomeName` based on the given generic type `N`. We keep the `SomeName` struct and `unsafe_new_name` private, so that external users cannot create new `impl Name` that may violate the uniqueness guarantee. 84 | 85 | We can see that at this stage the name creation is still unsafe, as user can still bind the closure expression to a variable like `let f = ||{}`, and then the resulting test `same(unsafe_new_name(f), unsafe_new_name(f))` would pass. Similarly, if we define a function like `foo() -> impl Name` that calls `unsafe_new_name(||{})` inside the function, the resulting test `same(foo(), foo())` would still pass the compilation. 86 | 87 | ## Name Seed 88 | 89 | To guarantee that every opaque type behind an `impl Name` is unique, we need to ensure that not only the inner function that returns `impl Name` is generic, but also _all_ possible functions that return `impl Name` must be generic as well. 90 | 91 | While it is possible to ask end users to provide a unique type using `||{}` at each function definition and and call, doing so can be error prone and confusing. Instead, we want to define a _name seed_ that is a unique type itself, and use it to generate new names. 92 | 93 | We implement the `Seed` type as follows: 94 | 95 | ```rust,compile_fail 96 | use core::marker::PhantomData; 97 | 98 | pub trait Name {} 99 | struct SomeName(PhantomData); 100 | impl Name for SomeName {} 101 | 102 | pub struct Seed(PhantomData); 103 | 104 | impl Seed { 105 | pub fn new_name(self) -> impl Name 106 | { 107 | SomeName::(PhantomData) 108 | } 109 | 110 | pub fn replicate(self) -> (impl Seed, impl Seed) 111 | { 112 | (Seed(PhantomData::), Seed(PhantomData::)) 113 | } 114 | } 115 | 116 | fn test(seed: impl Seed) { 117 | fn same(_: T, _: T) {} 118 | let (seed1, seed2) = seed.replicate(); 119 | same(seed1, seed2); // error 120 | same(seed1.new_name(), seed2.new_name()); // error 121 | } 122 | ``` 123 | 124 | The type `Seed` is parameterized by a name `N` and provides two methods. The first method `new_name(self)` _consumes_ the seed and returns a new `impl Name` (notice the lack of `&` in `self`). Since the seed is consumed when generating the name, the same seed cannot be used to generate another new name of the same type. 125 | 126 | Although the seed is consumed during name generation, the second method `replicate(self)` consumes the original seed, and returns two new seeds with unique names in the form of `impl Seed`. Notice that the two `impl Seed` returned by `replicate` are considered different types by Rust, even when they have the same underlying concrete type `Seed`. By calling `replicate` one or more times, we will be able to generate multiple names with unique types. 127 | 128 | Since there is no public constructor function to create new `Seed` value, the only way external users can create new seed is by replicating existing seeds. In this way, external functions would have to always accept `impl Seed` as an argument somewhere along the function calls to be able to generate new names. 129 | 130 | By treating our `test` function as an external function, it is forced to accept a `impl Seed` in order to generate new `impl Name`s. We first use `seed.replicate()` to create two new seeds `seed1` and `seed2`. When we compile the code, we can find out that the test `same(seed1, seed2)` fails, indicating that the two replicated seeds have different types. Similarly, the test `same(seed1.new_name(), seed2.new_name())` fails because the two names are generated from different seeds. It is also not possible to do something like `same(seed.new_name(), seed.new_name())`, because the affine type system of Rust consumes the seed during name generation and to not allow the seed to be reused. 131 | 132 | ## Named Values 133 | 134 | The `Seed` type we defined earlier provides a `new_name` method that returns unique `impl Name`. While having a unique name is not very useful on its own, it can be used to define a `Named` struct to assign unique names to a given value of type `T`. The `Named` struct is defined and used as follows: 135 | 136 | ```rust,compile_fail 137 | use core::marker::PhantomData; 138 | 139 | pub struct Named(T, PhantomData); 140 | 141 | impl Named { 142 | pub fn value(&self) -> &T { &self.0 } 143 | pub fn into_value(self) -> T { self.0 } 144 | } 145 | 146 | pub trait Name {} 147 | pub struct Seed(PhantomData); 148 | 149 | impl Seed { 150 | pub fn new_named(self, value: T) -> Named 151 | { 152 | Named::(value, PhantomData) 153 | } 154 | 155 | pub fn replicate(self) -> (impl Seed, impl Seed) 156 | { 157 | (Seed(PhantomData::), Seed(PhantomData::)) 158 | } 159 | } 160 | 161 | fn test(seed: impl Seed) { 162 | fn same(_: T, _: T) {} 163 | let (seed1, seed2) = seed.replicate(); 164 | same(seed1.new_named(1), seed2.new_named(1)); // error 165 | } 166 | ``` 167 | 168 | The struct `Named` is essentially a newtype wrapper around `T`, with the underlying value kept private. The `Named` type provides two public methods, `value` for getting a reference to the underlying value, and `into_value` to convert the named value to the underlying value. 169 | 170 | The `Seed` type now provides a `new_named` method that accepts an owned value of type `T`, and returns a `Named`. Because the `impl Name` is nested inside `Named`, we can guarantee that the new name given to the value is unique, provided that the `Seed` type is unique. 171 | 172 | Similar to earlier, we can test that two named values indeed have different names by writing a `test` function that accepts a `impl Seed`. After replicating the seed, we can verify that the test `same(seed1.new_named(1), seed2.new_named(1))` fails with error during compilation. This shows that the `Named` vallues returned by the two calls to `new_name` are indeed unique. 173 | 174 | We can think of the type `Named` as being a _singleton type_, that is, a type with only one possible value. With Rust's affine type system, the singleton guarantee is even stronger that we can never have two Rust values of type `Named` with the same `Name` type. 175 | 176 | ## Unique Lifetime with Higher Ranked Trait Bounds 177 | 178 | Our setup for generating uniquely named values is mostly complete, provided we are able to hand over the first unique `Seed` value to the main function to start generating new names. But we cannot simply expose a function like `fn new_seed() -> impl Seed`, as we know that two calls to the same function will return two values of the same type, thereby making them non-unique. 179 | 180 | We know that in languages like Haskell, it is possible to generate unique types by using continuation-passing-style with _higher-ranked_ continuations. While Rust do not currently support higher-ranked types, it instead supports [_higher-ranked trait bounds_](https://rustc-dev-guide.rust-lang.org/traits/hrtb.html) (HRTB) which can be used in similar way. 181 | 182 | ```rust,compile_fail 183 | use core::marker::PhantomData; 184 | 185 | pub trait Name {} 186 | pub struct Life<'name>(PhantomData<*mut &'name ()>); 187 | impl<'name> Name for Life<'name> {} 188 | 189 | pub fn with_seed( 190 | cont: impl for<'name> FnOnce(Seed>) -> R 191 | ) -> R { 192 | cont(Seed(PhantomData)) 193 | } 194 | 195 | pub struct Named(T, PhantomData); 196 | 197 | impl Named { 198 | pub fn value(&self) -> &T { &self.0 } 199 | pub fn into_value(self) -> T { self.0 } 200 | } 201 | 202 | pub struct Seed(PhantomData); 203 | 204 | impl Seed { 205 | pub fn new_named(self, value: T) -> Named 206 | { 207 | Named::(value, PhantomData) 208 | } 209 | 210 | pub fn replicate(self) -> (impl Seed, impl Seed) 211 | { 212 | (Seed(PhantomData::), Seed(PhantomData::)) 213 | } 214 | } 215 | 216 | fn same(_: T, _: T) {} 217 | 218 | with_seed(|seed| { 219 | let (seed1, seed2) = seed.replicate(); 220 | same(seed1, seed2); // error 221 | same(seed1.new_named(1), seed2.new_named(1)); // error 222 | }); 223 | 224 | with_seed(|seed1| { 225 | with_seed(|seed2| { 226 | same(seed1, seed2); // error 227 | same(seed1.new_named(1), seed2.new_named(1)); // error 228 | }); 229 | }); 230 | ``` 231 | 232 | We first come out with a different way of generating unique types, using the `Life` type that is parameterized by a _unique lifetime_. The struct is defined as `struct Life<'name>(PhantomData<*mut &'name ()>)`. The inner type `PhantomData<*mut &'name ()>` makes Rust treats `Life<'name>` as if it is a raw pointer of type `*mut &'name ()`. We specifically make it so that Rust treats `'name` as an _invariant_ phantom lifetime. This means that if we have two types `Life<'name1>` and `Life<'name2>`, Rust would consider them as different types even if there are partial overlaps such as `'name1: 'name2`. 233 | 234 | Using `Life`, we now simplify the problem of generating unique names to generating unique lifetimes. We then define the `with_seed` function, which accepts a continuation with a _higher-ranked trait bound_ `impl for<'name> FnOnce(Seed>) -> R`. The `for<'name>` part forces the contnuation closure to work with _all_ possible lifetimes. As a result, we can guarantee that the type `Life<'name>` is always unique inside the closure. By using `Life<'name>` as the unique type inside `Seed>`, we ensure that the seed type given to the continuation closure is also unique. 235 | 236 | We can now repeat the same test we had earlier, but now with the tests running inside the closure given to `with_seed`. We can verify that after replicating the seed, the tests `same(seed1, seed2)` and `same(seed1.new_named(1), seed2.new_named(1))` still fail with compilation error, indicating that the names generated are different. 237 | 238 | We can also repeat the same test with two nested calls to `with_seed`, thereby getting two separate fresh seeds `seed1` and `seed2`. Thanks to the magic of HRTB and invariant phantom lifetime, we can verify that even in this case the test `same(seed1, seed2)` still fails, indicating that Rust is treating the two underlying lifetimes differently. Similarly, the test `same(seed1.new_named(1), seed2.new_named(1))` also fails, indicating that names generated by two different seeds are indeed different. 239 | 240 | The techniques of using phantom lifetimes as names and using HRTB to generate new lifetimes is first explored in [GhostCell](http://plv.mpi-sws.org/rustbelt/ghostcell/). With that, we close the loop of unique name generation by requiring the top level program to generate the first seed using `with_seed`. Furthermore, since multiple calls to `with_seed` is safe, this means that there is no way for external users to do unsafe construction of two seeds or named values of the same type. From this, we can safely reason that assuming unsafe Rust is not used, whenever we get a value of type `Named`, the type `Name` is always uniquely assigned to the underlying value. 241 | --------------------------------------------------------------------------------