├── .editorconfig ├── .github └── workflows │ ├── ci.yaml │ └── publish.yaml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── bless.nu ├── rustfmt.toml ├── src └── lib.rs └── tests ├── trybuild.rs └── ui ├── crossed_streams.rs ├── crossed_streams.stderr ├── is_trans_compatible.rs └── is_trans_compatible.stderr /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | tests: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Install stable toolchain 16 | uses: dtolnay/rust-toolchain@stable 17 | 18 | - name: Run tests 19 | run: cargo test --all-features --no-fail-fast 20 | 21 | msrv: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v4 26 | 27 | - name: Install 1.56 toolchain 28 | uses: dtolnay/rust-toolchain@1.56 29 | 30 | - name: Check tests 31 | run: cargo test --all-features --no-run 32 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - "v*" 5 | 6 | jobs: 7 | publish: 8 | name: Publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | 14 | - name: Publish 15 | run: cargo publish 16 | env: 17 | CARGO_REGISTRY_TOKEN: ${{secrets.CARGO_REGISTRY_TOKEN}} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/**/usage.statistics.xml 10 | .idea/**/dictionaries 11 | .idea/**/shelf 12 | 13 | # Generated files 14 | .idea/**/contentModel.xml 15 | 16 | # Sensitive or high-churn files 17 | .idea/**/dataSources/ 18 | .idea/**/dataSources.ids 19 | .idea/**/dataSources.local.xml 20 | .idea/**/sqlDataSources.xml 21 | .idea/**/dynamic.xml 22 | .idea/**/uiDesigner.xml 23 | .idea/**/dbnavigator.xml 24 | 25 | # Gradle 26 | .idea/**/gradle.xml 27 | .idea/**/libraries 28 | 29 | # Gradle and Maven with auto-import 30 | # When using Gradle or Maven with auto-import, you should exclude module files, 31 | # since they will be recreated, and may cause churn. Uncomment if using 32 | # auto-import. 33 | # .idea/modules.xml 34 | # .idea/*.iml 35 | # .idea/modules 36 | # *.iml 37 | # *.ipr 38 | 39 | # CMake 40 | cmake-build-*/ 41 | 42 | # Mongo Explorer plugin 43 | .idea/**/mongoSettings.xml 44 | 45 | # File-based project format 46 | *.iws 47 | 48 | # IntelliJ 49 | out/ 50 | 51 | # mpeltonen/sbt-idea plugin 52 | .idea_modules/ 53 | 54 | # JIRA plugin 55 | atlassian-ide-plugin.xml 56 | 57 | # Cursive Clojure plugin 58 | .idea/replstate.xml 59 | 60 | # Crashlytics plugin (for Android Studio and IntelliJ) 61 | com_crashlytics_export_strings.xml 62 | crashlytics.properties 63 | crashlytics-build.properties 64 | fabric.properties 65 | 66 | # Editor-based Rest Client 67 | .idea/httpRequests 68 | 69 | # Android studio 3.1+ serialized cache file 70 | .idea/caches/build_file_checksums.ser 71 | 72 | ### Rust template 73 | # Generated by Cargo 74 | # will have compiled files and executables 75 | /target/ 76 | 77 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 78 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 79 | Cargo.lock 80 | 81 | # These are backup files generated by rustfmt 82 | **/*.rs.bk 83 | 84 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generativity" 3 | version = "1.1.0" 4 | edition = "2021" 5 | rust-version = "1.56" 6 | 7 | description = "Generation of unique invariant lifetimes" 8 | repository = "https://github.com/CAD97/generativity" 9 | keywords = ["generativity", "no-std", "unsafe-tools"] 10 | categories = ["no-std", "rust-patterns"] 11 | license = "MIT OR Apache-2.0" 12 | 13 | [dev-dependencies] 14 | trybuild = "1.0.85" 15 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019, 2022 Christopher Durham (CAD97) 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 | # Generativity [![Chat on Discord](https://img.shields.io/discord/496481045684944897?style=flat&logo=discord)][Discord] 2 | 3 | Generativity refers to the creation of a unique lifetime: one that the Rust 4 | borrow checker will not unify with any other lifetime. This can be used to 5 | brand types such that you know that you, and not another copy of you, created 6 | them. This is required for sound unchecked indexing and similar tricks. 7 | 8 | The first step of achieving generativity is an [invariant][variance] lifetime. 9 | Then the consumer must not be able to unify that lifetime with any other lifetime. 10 | 11 | Traditionally, this is achieved with a closure. If you have the user write their code in a 12 | `for<'a> fn(Invariant<'a>) -> _` callback, the local typechecking within this callback 13 | is forced to assume that it can be handed _any_ lifetime (the `for<'a>` bound), and that 14 | it cannot possibly use another lifetime, even `'static`, in its place (the invariance). 15 | 16 | This crate implements a different approach using macros and a `Drop`-based scope guard. 17 | When you call `generativity::make_guard!` to make a unique lifetime guard, the macro 18 | first creates an `Id` holding an invariant lifetime. It then puts within a `Drop` type 19 | a `&'id Id<'id>` reference. A different invocation of the macro has a different end timing 20 | to its tag lifetime, so the lifetimes cannot be unified. These types have no safe way to 21 | construct them other than via `make_guard!`, thus the lifetime is guaranteed unique. 22 | 23 | This effectively does the same thing as wrapping the rest of the function in an 24 | immediately invoked closure. We do some pre-work (create the tag and give the caller the 25 | guard), run the user code (the code after here, the closure previously), and then run 26 | some cleanup code after (in the drop implementation). This same technique of a macro 27 | hygiene hidden `impl Drop` can be used by most APIs that would normally use a closure 28 | argument, enabling them to avoid introducing a closure boundary to control flow "effect"s 29 | such as `async.await` and `?`. But this also comes with a subtle downside: being able to 30 | `.await` means that locals could be forgotten instead of dropped, and thus the drop glue 31 | must not be relied on to run for soundness. This is okay for this crate, since the actual 32 | drop impl is a no-op that just exists for the lifetime analysis impacts, but it means that 33 | more interesting cases like scoped threads still need to use a closure callback to ensure 34 | their soundness-critical cleanup gets run (e.g. to wait on and join the scoped threads). 35 | 36 | It's important to note that lifetimes are only trusted to carry lifetime brands when they 37 | are in fact invariant. Variant lifetimes, such as `&'a T`, can still be shrunk to fit the 38 | branded lifetime; `&'static T` can be used where `&'a T` is expected, for *any* `'a`. 39 | 40 | ## How does it work? 41 | 42 | ```rust 43 | // SAFETY: The lifetime given to `$name` is unique among trusted brands. 44 | // We know this because of how we carefully control drop timing here. 45 | // The branded lifetime's end is bound to be no later than when the 46 | // `branded_place` is invalidated at the end of scope, but also must be 47 | // no sooner than `lifetime_brand` is dropped, also at the end of scope. 48 | // Some other variant lifetime could be constrained to be equal to the 49 | // brand lifetime, but no other lifetime branded by `make_guard!` can, 50 | // as its brand lifetime has a distinct drop time from this one. QED 51 | let branded_place = unsafe { $crate::Id::new() }; 52 | #[allow(unused)] 53 | let lifetime_brand = unsafe { $crate::LifetimeBrand::new(&branded_place) }; 54 | // implicit when exiting scope: drop(lifetime_brand), as LifetimeBrand: Drop 55 | let $name = unsafe { $crate::Guard::new(branded_place) }; 56 | ``` 57 | 58 | A previous version of this crate emitted more code in the macro, and defined the 59 | `LifetimeBrand` type inline. This improved compiler errors on compiler versions 60 | from the time, but the current compiler produces an equivalently good or better 61 | error with `LifetimeBrand` defined in the `generativity` crate. 62 | 63 | The `LifetimeBrand` type is *not public API* and must not be used directly. It 64 | is not covered by stability guarantees; only usage of `make_guard!` and other 65 | documented APIs are considered stable. 66 | 67 | ## Huge generativity disclaimer 68 | 69 | That last point above is ***VERY*** important. We _cannot_ guarantee that the 70 | lifetime is fully unique. We can only guarantee that it's unique among trusted 71 | carriers. This applies equally to the traditional method: 72 | 73 | ```rust 74 | fn scope(f: F) 75 | where F: for<'id> FnOnce(Guard<'id>) 76 | { 77 | make_guard!(guard); 78 | f(guard); 79 | } 80 | 81 | fn unify<'a>(_: &'a (), _: &Guard<'a>) { 82 | // here, you have two `'a` which are equivalent to `guard`'s `'id` 83 | } 84 | 85 | fn main() { 86 | let place = (); 87 | make_guard!(guard); 88 | unify(&place, &guard); 89 | scope(|guard| { 90 | unify(&place, &guard); 91 | }) 92 | } 93 | ``` 94 | 95 | Other variant lifetimes can still shrink to unify with `'id`. What all this 96 | means is that you can't just trust any old `'id` lifetime floating around. It 97 | has to be carried in a trusted carrier, and one that wasn't created from an 98 | untrusted lifetime. 99 | 100 | ## Impl Disclaimer 101 | 102 | This relies on dead code (an `#[inline(always)]` no-op drop) to impact borrow 103 | checking. In theory, a sufficiently advanced borrow checker looking at the CFG 104 | after some inlining would be able to see that dropping `lifetime_brand` doesn't 105 | require the captured lifetime to be live, and destroy the uniqueness guarantee 106 | which we've created. This would be much more difficult with the higher-ranked 107 | closure formulation, but would still theoretically be possible with sufficient 108 | inlining. Thankfully, based on the direction around the unstable "borrowck 109 | eyepatch" which is the reason e.g. `Box<&'a T>` can be dropped at end of scope 110 | despite the `&'a T` borrow being invalidated beforehand, and the further 111 | stability implications of inferring whether a generic is "used" by `Drop::drop`, 112 | it seems like any such weakening of an explicit `impl Drop` "using" captured 113 | lifetimes in the eyes of borrowck will be opt-in. This crate won't opt in to 114 | such a feature, and thus will remain sound. 115 | 116 | ## Minimum supported Rust version 117 | 118 | The crate currently requires Rust 1.56. I have no intent of increasing the 119 | compiler version requirement of this crate beyond this. However, this is only 120 | guaranteed within a given minor version number. 121 | 122 | ## License 123 | 124 | Licensed under either of 125 | 126 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 127 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 128 | 129 | at your option. 130 | 131 | ### Contribution 132 | 133 | Unless you explicitly state otherwise, any contribution intentionally submitted 134 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall 135 | be dual licensed as above, without any additional terms or conditions. 136 | 137 | [Discord]: 138 | [variance]: 139 | -------------------------------------------------------------------------------- /bless.nu: -------------------------------------------------------------------------------- 1 | with-env [TRYBUILD overwrite] { 2 | cargo test --test trybuild 3 | } 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | newline_style = "Unix" 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), no_std)] 2 | 3 | //! Create a trusted carrier with a new lifetime that is guaranteed to be 4 | //! unique among other trusted carriers. When you call [`make_guard!`] to make a 5 | //! unique lifetime, the macro creates a [`Guard`] to hold it. This guard can be 6 | //! converted `into` an [`Id`], which can be stored in structures to uniquely 7 | //! "brand" them. A different invocation of the macro will produce a new 8 | //! lifetime that cannot be unified. The only way to construct these types is 9 | //! with [`make_guard!`] or `unsafe` code. 10 | //! 11 | //! ```rust 12 | //! use generativity::{Id, make_guard}; 13 | //! struct Struct<'id>(Id<'id>); 14 | //! make_guard!(a); 15 | //! Struct(a.into()); 16 | //! ``` 17 | //! 18 | //! This is the concept of "generative" lifetime brands. `Guard` and `Id` are 19 | //! [invariant](https://doc.rust-lang.org/nomicon/subtyping.html#variance) over 20 | //! their lifetime parameter, meaning that it is never valid to substitute or 21 | //! otherwise coerce `Id<'a>` into `Id<'b>`, for *any* concrete `'a` or `'b`, 22 | //! *including* the `'static` lifetime. 23 | //! 24 | //! Any invariant lifetime can be "trusted" to carry a brand, so long as they 25 | //! are known to be restricted to carrying a brand, and haven't been derived 26 | //! from some untrusted lifetime (or are completely unbound). When using this 27 | //! library, it is recommended to always use `Id<'id>` to carry the brand, as 28 | //! this reduces the risk of accidentally trusting an untrusted lifetime. 29 | //! Importantly, non-invariant lifetimes *cannot* be trusted, as the variance 30 | //! allows lifetimes to be contracted to match and copy the brand lifetime. 31 | //! 32 | //! To achieve lifetime invariance without `Id`, there are two standard ways: 33 | //! `PhantomData<&'a mut &'a ()>` and `PhantomData &'a ()>`. The 34 | //! former works because `&mut T` is invariant over `T`, and the latter works 35 | //! because `fn(T)` is *contra*variant over `T` and `fn() -> T` is *co*variant 36 | //! over `T`, which combines to *in*variance. Both are equivalent in this case 37 | //! with `T = ()`, but `fn(T) -> T` is generally preferred if the only purpose 38 | //! is to indicate invariance, as function pointers are a perfect cover for all 39 | //! auto traits (e.g. `Send`, `Sync`, `Unpin`, `UnwindSafe`, etc.) and thus 40 | //! only indicates invariance, whereas `&mut T` can carry further implication 41 | //! of "by example" use of `PhantomData`. 42 | 43 | use core_::fmt; 44 | use core_::marker::PhantomData; 45 | 46 | #[doc(hidden)] 47 | /// NOT STABLE PUBLIC API. Previously Used by the expansion of [`make_guard!`]. 48 | pub extern crate core as core_; 49 | 50 | /// A phantomdata-like type taking a single invariant lifetime. 51 | /// 52 | /// Used to manipulate and store the unique invariant lifetime obtained from 53 | /// [`Guard`]. Use `guard.into()` to create a new `Id`. 54 | /// 55 | /// Holding `Id<'id>` indicates that the lifetime `'id` is a trusted brand. 56 | /// `'id` will not unify with another trusted brand lifetime unless it comes 57 | /// from the same original brand (i.e. the same invocation of [`make_guard!`]). 58 | #[repr(transparent)] 59 | #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 60 | pub struct Id<'id> { 61 | phantom: PhantomData &'id ()>, 62 | } 63 | 64 | impl<'id> Id<'id> { 65 | /// Construct an `Id` with an unbounded lifetime. 66 | /// 67 | /// You should not need to use this function; use [`make_guard!`] instead. 68 | /// 69 | /// # Safety 70 | /// 71 | /// `Id` holds an invariant lifetime that must be derived from a generative 72 | /// brand. Using this function directly is the "I know what I'm doing" 73 | /// button; restrict the lifetime to a known brand immediately to avoid 74 | /// introducing potential unsoundness. 75 | pub unsafe fn new() -> Self { 76 | Id { 77 | phantom: PhantomData, 78 | } 79 | } 80 | } 81 | 82 | impl<'id> fmt::Debug for Id<'id> { 83 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 84 | f.debug_struct("#[invariant] 'id").finish() 85 | } 86 | } 87 | 88 | impl<'id> From> for Id<'id> { 89 | fn from(guard: Guard<'id>) -> Self { 90 | guard.id 91 | } 92 | } 93 | 94 | /// An invariant lifetime phantomdata-alike that is guaranteed to be unique 95 | /// with respect to other trusted invariant lifetimes. 96 | /// 97 | /// In effect, this means that `'id` is a "generative brand". Use [`make_guard`] 98 | /// to obtain a new `Guard`. 99 | #[repr(transparent)] 100 | #[derive(Eq, PartialEq)] 101 | pub struct Guard<'id> { 102 | #[allow(unused)] 103 | id: Id<'id>, 104 | } 105 | 106 | impl<'id> Guard<'id> { 107 | /// Construct a `Guard` with an unbound lifetime. 108 | /// 109 | /// You should not need to use this function; use [`make_guard!`] instead. 110 | /// 111 | /// # Safety 112 | /// 113 | /// `Guard` holds an invariant lifetime that must be an unused generative 114 | /// brand. Using this function directly is the "I know what I'm doing" 115 | /// button; restrict the lifetime to a known brand immediately to avoid 116 | /// introducing potential unsoundness. 117 | pub unsafe fn new(id: Id<'id>) -> Guard<'id> { 118 | Guard { id } 119 | } 120 | } 121 | 122 | impl<'id> fmt::Debug for Guard<'id> { 123 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 124 | f.debug_struct("#[unique] 'id").finish() 125 | } 126 | } 127 | 128 | #[doc(hidden)] 129 | /// NOT STABLE PUBLIC API. Used by the expansion of [`make_guard!`]. 130 | pub struct LifetimeBrand<'id> { 131 | phantom: PhantomData<&'id Id<'id>>, 132 | } 133 | 134 | impl<'id> Drop for LifetimeBrand<'id> { 135 | #[inline(always)] 136 | fn drop(&mut self) { 137 | // This impl purposefully left blank. The presence of a Drop impl gives 138 | // the `make_guard` type drop glue, dropping it at the end of scope. 139 | // Importantly, this ensures that the compiler has to consider `'id` 140 | // live at the point that this type is dropped, because this impl could 141 | // potentially use data borrowed that lifetime. #[inline(always)] just 142 | // serves to make it easier to optimize out the noop function call. 143 | } 144 | } 145 | 146 | #[doc(hidden)] 147 | /// NOT STABLE PUBLIC API. Used by the expansion of [`make_guard!`]. 148 | impl<'id> LifetimeBrand<'id> { 149 | #[doc(hidden)] 150 | #[inline(always)] 151 | /// NOT STABLE PUBLIC API. Used by the expansion of [`make_guard!`]. 152 | pub unsafe fn new(_: &'id Id<'id>) -> LifetimeBrand<'id> { 153 | // This function serves to entangle the `'id` lifetime, making it into 154 | // a proper lifetime brand. The `'id` region may open at any point, but 155 | // it must end in-between the drop timing of this `LifetimeBrand` and 156 | // the `Id` binding used to create it. 157 | LifetimeBrand { 158 | phantom: PhantomData, 159 | } 160 | } 161 | } 162 | 163 | /// Create a `Guard` with a unique invariant lifetime (with respect to other 164 | /// trusted/invariant lifetime brands). 165 | /// 166 | /// Multiple `make_guard` lifetimes will always fail to unify: 167 | /// 168 | /// ```rust,compile_fail,E0597 169 | /// # // trybuild ui test tests/ui/crossed_streams.rs 170 | /// # use generativity::make_guard; 171 | /// make_guard!(a); 172 | /// make_guard!(b); 173 | /// dbg!(a == b); // ERROR (here == is a static check) 174 | /// ``` 175 | #[macro_export] 176 | macro_rules! make_guard { 177 | ($name:ident) => { 178 | // SAFETY: The lifetime given to `$name` is unique among trusted brands. 179 | // We know this because of how we carefully control drop timing here. 180 | // The branded lifetime's end is bound to be no later than when the 181 | // `branded_place` is invalidated at the end of scope, but also must be 182 | // no sooner than `lifetime_brand` is dropped, also at the end of scope. 183 | // Some other variant lifetime could be constrained to be equal to the 184 | // brand lifetime, but no other lifetime branded by `make_guard!` can, 185 | // as its brand lifetime has a distinct drop time from this one. QED 186 | let branded_place = unsafe { $crate::Id::new() }; 187 | #[allow(unused)] 188 | let lifetime_brand = unsafe { $crate::LifetimeBrand::new(&branded_place) }; 189 | let $name = unsafe { $crate::Guard::new(branded_place) }; 190 | }; 191 | } 192 | 193 | #[cfg(test)] 194 | mod test { 195 | use super::*; 196 | use std::panic::{RefUnwindSafe, UnwindSafe}; 197 | 198 | #[test] 199 | fn dont_error_in_general() { 200 | make_guard!(a); 201 | make_guard!(b); 202 | assert_eq!(a, a); 203 | assert_eq!(b, b); 204 | } 205 | 206 | #[test] 207 | fn test_oibits() { 208 | fn assert_oibits(_: &T) 209 | where 210 | T: Send + Sync + Unpin + UnwindSafe + RefUnwindSafe, 211 | { 212 | } 213 | 214 | make_guard!(a); 215 | assert_oibits(&a); 216 | let id: Id<'_> = a.into(); 217 | assert_oibits(&id); 218 | 219 | // const compatible (e.g. const_refs_to_cell, const destructor) 220 | const fn _const_id(_: Id<'_>) {} 221 | const fn _const_ref_id(_: &'_ Id<'_>) {} 222 | const fn _const_guard(_: Guard<'_>) {} 223 | const fn _const_ref_guard(_: &'_ Guard<'_>) {} 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /tests/trybuild.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn ui() { 3 | let t = trybuild::TestCases::new(); 4 | t.compile_fail("tests/ui/*.rs"); 5 | } 6 | -------------------------------------------------------------------------------- /tests/ui/crossed_streams.rs: -------------------------------------------------------------------------------- 1 | use generativity::make_guard; 2 | 3 | fn main() { 4 | make_guard!(a); 5 | make_guard!(b); 6 | dbg!(a == b); // ERROR (here == is a static check) 7 | } 8 | -------------------------------------------------------------------------------- /tests/ui/crossed_streams.stderr: -------------------------------------------------------------------------------- 1 | error[E0597]: `branded_place` does not live long enough 2 | --> tests/ui/crossed_streams.rs:5:5 3 | | 4 | 5 | make_guard!(b); 5 | | ^^^^^^^^^^^^^^ 6 | | | 7 | | borrowed value does not live long enough 8 | | binding `branded_place` declared here 9 | 6 | dbg!(a == b); // ERROR (here == is a static check) 10 | 7 | } 11 | | - 12 | | | 13 | | `branded_place` dropped here while still borrowed 14 | | borrow might be used here, when `lifetime_brand` is dropped and runs the `Drop` code for type `LifetimeBrand` 15 | | 16 | = note: values in a scope are dropped in the opposite order they are defined 17 | = note: this error originates in the macro `make_guard` (in Nightly builds, run with -Z macro-backtrace for more info) 18 | -------------------------------------------------------------------------------- /tests/ui/is_trans_compatible.rs: -------------------------------------------------------------------------------- 1 | // see cad97/generativity#13 and rust-lang/rust#78586 2 | // if this fails in crater *please* ping me; I *want* this to not hit the lint! 3 | #![deny(repr_transparent_external_private_fields)] // this should not trigger 4 | 5 | use generativity::{make_guard, Id}; 6 | 7 | #[repr(transparent)] 8 | pub struct BOption<'id, T>(Option, Id<'id>); // this should work 9 | 10 | fn main() { 11 | make_guard!(a); 12 | let _ = BOption(Some(0), a.into()); 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/is_trans_compatible.stderr: -------------------------------------------------------------------------------- 1 | error: zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types 2 | --> tests/ui/is_trans_compatible.rs:8:39 3 | | 4 | 8 | pub struct BOption<'id, T>(Option, Id<'id>); // this should work 5 | | ^^^^^^^ 6 | | 7 | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! 8 | = note: for more information, see issue #78586 9 | = note: this struct contains `Id<'id>`, which contains private fields, and makes it not a breaking change to become non-zero-sized in the future. 10 | note: the lint level is defined here 11 | --> tests/ui/is_trans_compatible.rs:3:9 12 | | 13 | 3 | #![deny(repr_transparent_external_private_fields)] // this should not trigger 14 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 15 | --------------------------------------------------------------------------------