├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src └── lib.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Install stable rust 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | override: true 18 | profile: minimal 19 | - uses: actions/checkout@v2 20 | - name: Build 21 | uses: actions-rs/cargo@v1 22 | with: 23 | command: build 24 | args: --all --all-features 25 | - name: Test 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: test 29 | args: --all --all-features 30 | 31 | clippy-check: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Install stable rust 35 | uses: actions-rs/toolchain@v1 36 | with: 37 | toolchain: stable 38 | override: true 39 | profile: minimal 40 | components: clippy 41 | - uses: actions/checkout@v2 42 | - uses: actions-rs/clippy-check@v1 43 | with: 44 | token: ${{ secrets.GITHUB_TOKEN }} 45 | args: --all --all-features 46 | 47 | rustfmt-check: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - name: Install stable rust 51 | uses: actions-rs/toolchain@v1 52 | with: 53 | toolchain: stable 54 | override: true 55 | profile: minimal 56 | components: rustfmt 57 | - uses: actions/checkout@v2 58 | - uses: actions-rs/cargo@v1 59 | with: 60 | command: fmt 61 | args: --all -- --check 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "diplomatic-bag" 3 | description = "A wrapper type that allows you to send `!Send` types to different threads." 4 | version = "0.3.1" 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/Skepfyr/DiplomaticBag" 8 | readme = "README.md" 9 | keywords = ["send", "sync"] 10 | categories = ["rust-patterns", "concurrency"] 11 | 12 | exclude = [".github"] 13 | 14 | [dependencies] 15 | crossbeam-channel = "0.5" 16 | once_cell = "1" 17 | 18 | [dev-dependencies] 19 | rand = "0.8" 20 | slotmap = "1" 21 | static_assertions = "1" 22 | version-sync = "0.9" 23 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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 [yyyy] [name of copyright owner] 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 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jack Rickard 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 | # Diplomatic Bag 2 | [![Crates.io](https://img.shields.io/crates/v/diplomatic-bag.svg)](https://crates.io/crates/diplomatic-bag) 3 | [![API reference](https://docs.rs/diplomatic-bag/badge.svg)](https://docs.rs/diplomatic-bag/) 4 | [![License](https://img.shields.io/badge/license-MIT_OR_Apache--2.0-blue.svg)]( 5 | https://github.com/Skepfyr/DiplomaticBag#license) 6 | [![Rust](https://github.com/Skepfyr/DiplomaticBag/workflows/Rust/badge.svg)](https://github.com/Skepfyr/DiplomaticBag/actions?query=workflow%3ARust+branch%3Amaster) 7 | 8 | Hide `!Send` and `!Sync` types inside a [`DiplomaticBag`](https://en.wikipedia.org/wiki/Diplomatic_bag) which can be sent between thread freely. 9 | 10 | ```rust 11 | let one = DiplomaticBag::new(|| Rc::new(RefCell::new(1))); 12 | let two = DiplomaticBag::new(|| Rc::new(RefCell::new(2))); 13 | let three: u8 = std::thread::spawn(|| { 14 | one.as_ref() 15 | .zip(two.as_ref()) 16 | .map(|(one, two)| *one.borrow() + *two.borrow()) 17 | .into_inner() 18 | }).join()?; 19 | ``` 20 | (I don't know why you'd want to do this ^, but I promise this comes in handy for `!Send` types not in std, for example, [cpal's `Stream` type](https://docs.rs/cpal/*/cpal/struct.Stream.html).) 21 | 22 | 23 | ## How it works 24 | 25 | All the operations on values inside `DiplomaticBags` are sent to a worker thread (the home country 😉). 26 | This means the values are only ever read on one thread, allowing the `DiplomaticBag` to be `Send` even if the type it contains is not. 27 | This model does have some drawbacks, see [the docs](https://docs.rs/diplomatic-bag/) for more information. 28 | 29 | ## License 30 | 31 | Licensed under either of 32 | 33 | * Apache License, Version 2.0 34 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 35 | * MIT license 36 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 37 | 38 | at your option. 39 | 40 | ## Contribution 41 | 42 | Unless you explicitly state otherwise, any contribution intentionally submitted 43 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 44 | dual licensed as above, without any additional terms or conditions. 45 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Diplomatic Bag 2 | //! A mechanism for dealing with [`!Send`][`Send`] types when you really need 3 | //! them to be [`Send`]. 4 | //! 5 | //! This library provides the [`DiplomaticBag`] type that is [`Send`] and 6 | //! [`Sync`] even if the type it wraps is not. It does this by preventing direct 7 | //! access to the wrapped type but instead provides methods for interacting with 8 | //! it on a thread that it never leaves. 9 | //! 10 | //! This is useful for when you have a [`!Send`][`Send`] type (usually an FFI 11 | //! type) that you need store for a long period of time, and needs to be 12 | //! accessible from multiple threads, for example, in async code. 13 | //! 14 | //! # Examples 15 | //! ``` 16 | //! # use diplomatic_bag::DiplomaticBag; 17 | //! # use std::{cell::RefCell, rc::Rc}; 18 | //! // `Rc` is neither `Send` nor `Sync` 19 | //! let foo = DiplomaticBag::new(|_| Rc::new(RefCell::new(0))); 20 | //! 21 | //! std::thread::spawn({ 22 | //! let foo = foo.clone(); 23 | //! move || { 24 | //! foo.as_ref().map(|_, rc| { 25 | //! *rc.borrow_mut() = 1; 26 | //! }); 27 | //! } 28 | //! }); 29 | //! ``` 30 | //! Now, being able to send an `Rc` around isn't very useful, but this comes in 31 | //! handy when dealing with FFI types that must remain on the same thread for 32 | //! their existence. 33 | 34 | #![doc(html_root_url = "https://docs.rs/diplomatic-bag/0.3.1")] 35 | #![warn( 36 | keyword_idents, 37 | rustdoc::missing_crate_level_docs, 38 | missing_debug_implementations, 39 | missing_docs, 40 | non_ascii_idents 41 | )] 42 | #![forbid(unsafe_op_in_unsafe_fn)] 43 | 44 | use crossbeam_channel::{bounded, unbounded, Sender}; 45 | use once_cell::sync::Lazy; 46 | use std::{ 47 | fmt, 48 | marker::PhantomData, 49 | mem::{self, ManuallyDrop}, 50 | ptr, 51 | thread::ThreadId, 52 | }; 53 | 54 | /// The (sender for) the thread that all the values live on. This is lazily 55 | /// created when the first `DiplomaticBag` is created, but will never shut down. 56 | static THREAD_SENDER: Lazy<(Sender, ThreadId)> = Lazy::new(|| { 57 | let (sender, receiver) = unbounded::(); 58 | let thread = std::thread::spawn({ 59 | move || { 60 | while let Ok(closure) = receiver.recv() { 61 | closure(); 62 | } 63 | } 64 | }); 65 | (sender, thread.thread().id()) 66 | }); 67 | 68 | /// A wrapper around a `T` that always implements [`Send`] and [`Sync`], but 69 | /// doesn't allow direct access to it's internals. 70 | /// 71 | /// For example, this doesn't compile: 72 | /// ```compile_fail 73 | /// let mut foo = 0; 74 | /// // `*mut T` doesn't implement `Send` or `Sync` 75 | /// let bar = (&mut foo) as *mut (); 76 | /// std::thread::spawn(|| bar); 77 | /// ``` 78 | /// but this will: 79 | /// ``` 80 | /// # use diplomatic_bag::DiplomaticBag; 81 | /// let mut foo = (); 82 | /// // `*mut T` doesn't implement `Send` or `Sync`, 83 | /// // but `DiplomaticBag<*mut T>` does. 84 | /// let bar = DiplomaticBag::new(|_| (&mut foo) as *mut ()); 85 | /// std::thread::spawn(|| bar); 86 | /// ``` 87 | /// 88 | /// # Panics 89 | /// All `DiplomaticBag`s share the same underlying thread, so if any panic, then 90 | /// every bag immediately becomes unusable, and no new bags are able to be 91 | /// created. This also means that the destructors of every value alive at that 92 | /// point will never be run, potentially leaking some resources. Most of the 93 | /// functions on a bag will panic if the underlying thread has stopped for any 94 | /// reason. 95 | /// 96 | /// # Blocking 97 | /// Another consequence of every bag using the same thread is that no two values 98 | /// can be modified concurrently, essentially sharing a lock on the thread. In 99 | /// general, you should be protected from this; [`run`] and all the methods on 100 | /// this type will detect if they are being run on the worker thread and will 101 | /// avoid "taking the lock", alternatively you can use the [`BaggageHandler`] to 102 | /// wrap and unwrap diplomatic bags safely. 103 | /// You should aim to do as little computation as possible inside the closures 104 | /// you provide to the functions on this type to prevent your code from blocking 105 | /// the progress of others. All functions block until they have completed 106 | /// executing the closure on the worker thread. 107 | pub struct DiplomaticBag { 108 | /// The actual value we are storing, wrapped in an [`Untouchable`] so 109 | /// that we don't accidentally run code on it, for example drop code. 110 | value: Untouchable, 111 | } 112 | 113 | impl DiplomaticBag { 114 | /// Create a new `DiplomaticBag` by invoking the provided closure and 115 | /// wrapping up the value that it produces. For why you would want to do 116 | /// this look at the type-level or crate-level docs. 117 | pub fn new(f: F) -> Self 118 | where 119 | F: FnOnce(BaggageHandler) -> T, 120 | F: Send, 121 | { 122 | run(|handler| DiplomaticBag { 123 | value: Untouchable::new(f(handler)), 124 | }) 125 | } 126 | 127 | /// Maps a `DiplomaticBag` to a `DiplomaticBag`, by running a closure 128 | /// on the wrapped value. 129 | /// 130 | /// This closure must be [`Send`] as it will run on a worker thread. It 131 | /// should also not panic, if it does all other active bags will leak, see 132 | /// the type-level docs for more information. 133 | /// 134 | /// If you need to access the contents of multiple bags simultaneously, you can 135 | /// use the provided [`BaggageHandler`], alternatively see the 136 | /// [`zip()`][Self::zip()] method. 137 | /// 138 | /// # Panics 139 | /// This function will panic if there is an issue with the underlying worker 140 | /// thread, which is usually caused by this or another bag panicking. 141 | /// 142 | /// # Examples 143 | /// ``` 144 | /// # use diplomatic_bag::DiplomaticBag; 145 | /// # use std::{cell::RefCell, rc::Rc}; 146 | /// let foo = DiplomaticBag::new(|_| Rc::new(RefCell::new(5))); 147 | /// let five = foo.map(|_, foo| { 148 | /// Rc::try_unwrap(foo).unwrap().into_inner() 149 | /// }); 150 | /// # assert_eq!(5, five.into_inner()); 151 | /// ``` 152 | pub fn map(self, f: F) -> DiplomaticBag 153 | where 154 | F: FnOnce(BaggageHandler, T) -> U, 155 | F: Send, 156 | { 157 | run(move |handler| { 158 | // Safety: 159 | // `into_inner` can only be called on the worker thread, as it gives 160 | // access to a value of type `T` and `T` isn't necessarily `Send`, 161 | // however that's where this will run. 162 | let value = unsafe { self.into_inner_unchecked() }; 163 | DiplomaticBag { 164 | value: Untouchable::new(f(handler, value)), 165 | } 166 | }) 167 | } 168 | 169 | /// Maps a `DiplomaticBag` to a `U`, by running a closure on the wrapped 170 | /// value. 171 | /// 172 | /// This closure must be [`Send`] as it will run on a worker thread. It 173 | /// should also not panic, if it does all other active bags will leak, see 174 | /// the type-level docs for more information. 175 | /// 176 | /// This function is especially useful for mapping over wrapper types like 177 | /// `Vec`, `Result`, `Option` (although see [`transpose()`][Self::transpose()] 178 | /// for those last two). It allows this by giving a [`BaggageHandler`] to 179 | /// the closure, which provides methods to wrap and unwrap `DiplomaticBag`s. 180 | /// 181 | /// # Panics 182 | /// This function will panic if there is an issue with the underlying worker 183 | /// thread, which is usually caused by this or another bag panicking. 184 | /// 185 | /// # Examples 186 | /// ``` 187 | /// # use diplomatic_bag::DiplomaticBag; 188 | /// let foo = DiplomaticBag::new(|_| vec!["ho"; 3]); 189 | /// let foo: Vec<_> = foo.and_then(|handler, foo| { 190 | /// foo.into_iter().map(|item| handler.wrap(item)).collect() 191 | /// }); 192 | /// # assert_eq!(&"ho", foo[0].as_ref().into_inner()); 193 | /// ``` 194 | pub fn and_then(self, f: F) -> U 195 | where 196 | U: Send, 197 | F: FnOnce(BaggageHandler, T) -> U, 198 | F: Send, 199 | { 200 | run(move |handler| { 201 | // Safety: 202 | // `into_inner` can only be called on the worker thread, as it gives 203 | // access to a value of type `T` and `T` isn't necessarily `Send`, 204 | // however that's where this will run. 205 | let value = unsafe { self.into_inner_unchecked() }; 206 | f(handler, value) 207 | }) 208 | } 209 | 210 | /// Combine a `DiplomaticBag` and a `DiplomaticBag` into a 211 | /// `DiplomaticBag<(T, U)>`. 212 | /// 213 | /// This is useful when combined with [`map()`][Self::map()] to allow 214 | /// interacting with the internals of multiple bags simultaneously. 215 | /// 216 | /// # Examples 217 | /// ``` 218 | /// # use diplomatic_bag::DiplomaticBag; 219 | /// let one = DiplomaticBag::new(|_| 1); 220 | /// let two = DiplomaticBag::new(|_| 2); 221 | /// let three = one.zip(two).map(|_, (one, two)| one + two); 222 | /// # assert_eq!(3, three.into_inner()); 223 | /// ``` 224 | pub fn zip(self, other: DiplomaticBag) -> DiplomaticBag<(T, U)> { 225 | // Safety: 226 | // We immediately wrap up the values returned by `into_inner_unchecked` 227 | // so they spend the minimum amount of time on this thread. The only 228 | // danger here is them accidentally getting dropped on this thread, but 229 | // none of these functions can panic. 230 | let value = unsafe { 231 | Untouchable::new((self.into_inner_unchecked(), other.into_inner_unchecked())) 232 | }; 233 | DiplomaticBag { value } 234 | } 235 | 236 | /// Converts a `&DiplomaticBag` into a `DiplomaticBag<&T>`. 237 | /// 238 | /// # Examples 239 | /// ``` 240 | /// # use diplomatic_bag::DiplomaticBag; 241 | /// let a = DiplomaticBag::new(|_| 0); 242 | /// let b = a.as_ref().map(|_, a| a.clone()); 243 | /// # assert_eq!(a, b); 244 | /// ``` 245 | pub fn as_ref(&self) -> DiplomaticBag<&T> { 246 | // Safety: 247 | // `as_ref` produces a `&T`, which is not necessarily `Send` as `T` may 248 | // not be `Sync`. However it is immediately wrapped in an `Untouchable` 249 | // again and `&T`s are only read explicitly. 250 | let value = unsafe { Untouchable::new(self.value.as_ref()) }; 251 | DiplomaticBag { value } 252 | } 253 | 254 | /// Converts a `&mut DiplomaticBag` into a `DiplomaticBag<&mut T>`. 255 | /// 256 | /// # Examples 257 | /// ``` 258 | /// # use diplomatic_bag::DiplomaticBag; 259 | /// let mut a = DiplomaticBag::new(|_| 1); 260 | /// let mut b = DiplomaticBag::new(|_| 2); 261 | /// a.as_mut().zip(b.as_mut()).map(|_, (a, b)| { 262 | /// std::mem::swap(a, b); 263 | /// }); 264 | /// # assert_eq!(2, a.into_inner()); 265 | /// # assert_eq!(1, b.into_inner()); 266 | /// ``` 267 | pub fn as_mut(&mut self) -> DiplomaticBag<&mut T> { 268 | // Safety: 269 | // `as_mut` produces a `&mut T`, which is not necessarily `Send` as `T` 270 | // may not be `Send`. However it is immediately wrapped in an 271 | // `Untouchable` again and `&mut T`s are only read explicitly. 272 | let value = unsafe { Untouchable::new(self.value.as_mut()) }; 273 | DiplomaticBag { value } 274 | } 275 | 276 | /// Unwrap the `DiplomaticBag` and retrieve the inner value. 277 | /// 278 | /// # Safety 279 | /// This must only be called from the worker thread if `T` is not 280 | /// `Send` as it was created on that thread. 281 | unsafe fn into_inner_unchecked(self) -> T { 282 | // Unfortunately you can't destructure `Drop` types at the moment, this 283 | // is the current workaround: pull all the fields out and then forget 284 | // the outer struct so the drop code isn't run. Note that the memory is 285 | // still freed as the bag was moved into this function, but every field 286 | // should be read out so that all of their destructors are run. 287 | let value = unsafe { ptr::read(&self.value) }; 288 | mem::forget(self); 289 | // Safety: We forward these safety requirements to our caller. 290 | unsafe { value.into_inner() } 291 | } 292 | } 293 | 294 | impl DiplomaticBag { 295 | /// Unwrap a value in a [`DiplomaticBag`], allowing it to be used on this 296 | /// thread. 297 | /// 298 | /// This is only possible if `T` is [`Send`], as otherwise accessing the 299 | /// value on an arbitrary thread is UB. However, if `T` is [`Sync`] then you 300 | /// can run the following to obtain a `&T`. 301 | /// ``` 302 | /// # fn foo(bag: &diplomatic_bag::DiplomaticBag) -> &T { 303 | /// bag.as_ref().into_inner() 304 | /// # } 305 | /// ``` 306 | /// 307 | /// # Examples 308 | /// ``` 309 | /// # use diplomatic_bag::DiplomaticBag; 310 | /// let one = DiplomaticBag::new(|_| 1); 311 | /// let two = DiplomaticBag::new(|_| 2); 312 | /// let eq = one.zip(two).map(|_, (one, two)| one == two).into_inner(); 313 | /// # assert!(!eq); 314 | /// ``` 315 | pub fn into_inner(self) -> T { 316 | unsafe { self.into_inner_unchecked() } 317 | } 318 | } 319 | 320 | impl DiplomaticBag> { 321 | /// Convert a `DiplomaticBag>` into a 322 | /// `Result, DiplomaticBag>`. 323 | /// 324 | /// # Examples 325 | /// ``` 326 | /// # use diplomatic_bag::DiplomaticBag; 327 | /// fn foo() -> Result, String> { 328 | /// let bag = DiplomaticBag::new(|_| { 329 | /// Ok(()) 330 | /// }); 331 | /// bag.transpose().map_err(|err| err.into_inner()) 332 | /// } 333 | /// ``` 334 | pub fn transpose(self) -> Result, DiplomaticBag> { 335 | // Safety: 336 | // Unclear, this isn't safe by the letter of the law as we read the 337 | // bytes of inner to determine if it's `Ok` or `Err`. However, although 338 | // not yet defined, `Send` and `Sync` are probably safety invariants, 339 | // not validity invariants. This means we can violate the contracts in 340 | // unsafe code soundly, as long as the T is never exposed on a different 341 | // thread to (uncontrolled) safe code. 342 | // 343 | // This only reads whether this is `Ok` or `Err` and this method cannot 344 | // panic, so no code that uses the thread invariant can observe T or E. 345 | let inner = unsafe { self.into_inner_unchecked() }; 346 | match inner { 347 | Ok(val) => Ok(DiplomaticBag { 348 | value: Untouchable::new(val), 349 | }), 350 | Err(err) => Err(DiplomaticBag { 351 | value: Untouchable::new(err), 352 | }), 353 | } 354 | } 355 | } 356 | 357 | impl DiplomaticBag> { 358 | /// Convert a `DiplomaticBag>` into a 359 | /// `Option>`. 360 | /// 361 | /// # Examples 362 | /// ``` 363 | /// # use diplomatic_bag::DiplomaticBag; 364 | /// DiplomaticBag::new(|_| Some(())).transpose().unwrap_or_default(); 365 | /// ``` 366 | pub fn transpose(self) -> Option> { 367 | // Safety: 368 | // Same as above `transpose` for `DiplomaticBag>`. 369 | let inner = unsafe { self.into_inner_unchecked() }; 370 | inner.map(|val| DiplomaticBag { 371 | value: Untouchable::new(val), 372 | }) 373 | } 374 | } 375 | 376 | /// `Drop` the inner type when the `DiplomaticBag` is dropped. 377 | /// 378 | /// Ideally, this would only be implemented when `T` is `Drop` but `Drop` must 379 | /// be implemented for all specializations of a generic type or none. However, 380 | /// it does use [`std::mem::needs_drop`] to identify if the drop code needs to 381 | /// be run. 382 | impl Drop for DiplomaticBag { 383 | fn drop(&mut self) { 384 | if !std::mem::needs_drop::() { 385 | // If no drop code needs to be run for `T` we are fine to simply 386 | // deallocate it. 387 | return; 388 | } 389 | let _ = try_run(|_| { 390 | // Safety: 391 | // The inner value must only be accessed from the worker thread, and 392 | // that is where this closure will run. 393 | unsafe { 394 | Untouchable::drop(&mut self.value); 395 | } 396 | }); 397 | } 398 | } 399 | 400 | /// This `Debug` impl currently won't forward any formatting specifiers except 401 | /// for the `alternate` specifier (`#`). 402 | impl fmt::Debug for DiplomaticBag { 403 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 404 | struct AsIs(String); 405 | 406 | impl fmt::Debug for AsIs { 407 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 408 | f.write_str(&self.0) 409 | } 410 | } 411 | 412 | let alt = f.alternate(); 413 | 414 | // Basically, run Debug on the worker thread, then send the resulting String back 415 | let res = self 416 | .as_ref() 417 | .map(move |_, this| { 418 | if alt { 419 | format!("{:#?}", this) 420 | } else { 421 | format!("{:?}", this) 422 | } 423 | }) 424 | .into_inner(); 425 | 426 | let res = AsIs(res); // So that we don't format it with quotes, etc. 427 | 428 | f.debug_struct("DiplomaticBag") 429 | .field("inner", &res) 430 | .finish() 431 | } 432 | } 433 | 434 | // We can, however, implement a bunch of other useful standard traits, as below. 435 | // Annoyingly, `serde::Serialize`, and `serde::Deserialize` both suffer from a worse 436 | // problem than `Debug` does. They have over 30 methods that would each need to 437 | // syncronise with the worker thread, rather than just a single result that comes 438 | // out the end. `Display` also has the same problem, and since it is more intended 439 | // for non-technical users, an implementation that doesn't forward all the formatting 440 | // parameters would be worse. `From`, and `TryFrom` fail due to the orphan rules. 441 | // `AsRef` and `AsMut` can be implemented but conflict with the existing `as_ref` 442 | // and `as_mut` methods, and I'm not sure the confusion is worth it. 443 | 444 | impl Default for DiplomaticBag { 445 | fn default() -> Self { 446 | DiplomaticBag::new(|_| T::default()) 447 | } 448 | } 449 | 450 | impl Clone for DiplomaticBag { 451 | fn clone(&self) -> Self { 452 | self.as_ref().map(|_, val| T::clone(val)) 453 | } 454 | } 455 | 456 | impl PartialEq for DiplomaticBag { 457 | fn eq(&self, other: &Self) -> bool { 458 | self.as_ref() 459 | .zip(other.as_ref()) 460 | .map(|_, (this, other)| T::eq(this, other)) 461 | .into_inner() 462 | } 463 | } 464 | impl Eq for DiplomaticBag {} 465 | 466 | impl PartialOrd for DiplomaticBag { 467 | fn partial_cmp(&self, other: &Self) -> Option { 468 | self.as_ref() 469 | .zip(other.as_ref()) 470 | .map(|_, (this, other)| T::partial_cmp(this, other)) 471 | .into_inner() 472 | } 473 | } 474 | impl Ord for DiplomaticBag { 475 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 476 | self.as_ref() 477 | .zip(other.as_ref()) 478 | .map(|_, (this, other)| T::cmp(this, other)) 479 | .into_inner() 480 | } 481 | } 482 | 483 | /// A type that allows wrapping and unwrapping [`DiplomaticBag`]s inside the 484 | /// execution context of another bag. 485 | /// 486 | /// This allows computations on the wrapped values of multiple bags, and 487 | /// provides a mechanism for returning `!Send` and `!Sync` types from 488 | /// computations done on values inside bags. 489 | /// 490 | /// # Examples 491 | /// ``` 492 | /// # use diplomatic_bag::DiplomaticBag; 493 | /// let one = DiplomaticBag::new(|_handler| 1); 494 | /// let two = DiplomaticBag::new(|_handler| 2); 495 | /// let three = one.and_then(|handler, one| { 496 | /// let three = one + handler.unwrap(two.as_ref()); 497 | /// handler.wrap(three) 498 | /// }); 499 | /// # assert_eq!(3, three.into_inner()); 500 | #[derive(Debug, Clone, Copy)] 501 | pub struct BaggageHandler(PhantomData<*mut ()>); 502 | 503 | impl BaggageHandler { 504 | /// Create a new `BaggageHandler`. 505 | /// 506 | /// # Safety 507 | /// This must only be called from the worker thread, as it allows safe 508 | /// wrapping and unwrapping of `DiplomaticBag`s. 509 | unsafe fn new() -> Self { 510 | Self(PhantomData) 511 | } 512 | 513 | /// Wrap a value in a [`DiplomaticBag`], allowing it to be sent to other 514 | /// threads even if it is not `Send`. 515 | /// 516 | /// # Examples 517 | /// ``` 518 | /// # use diplomatic_bag::DiplomaticBag; 519 | /// let foo: DiplomaticBag = DiplomaticBag::new(|_handler| 2); 520 | /// let bar: DiplomaticBag = 521 | /// foo.and_then(|handler, value| handler.wrap(value.clone())); 522 | /// ``` 523 | pub fn wrap(&self, value: T) -> DiplomaticBag { 524 | DiplomaticBag { 525 | value: Untouchable::new(value), 526 | } 527 | } 528 | /// Unwrap a value in a [`DiplomaticBag`], allowing it to be used inside 529 | /// the execution context of another bag. 530 | /// 531 | /// # Examples 532 | /// ``` 533 | /// # use diplomatic_bag::DiplomaticBag; 534 | /// let one = DiplomaticBag::new(|_handler| 1); 535 | /// let two = DiplomaticBag::new(|_handler| 2); 536 | /// let three = one.and_then(|handler, one| one + handler.unwrap(two)); 537 | /// # assert_eq!(3, three); 538 | /// ``` 539 | pub fn unwrap(&self, proxy: DiplomaticBag) -> T { 540 | unsafe { proxy.into_inner_unchecked() } 541 | } 542 | } 543 | 544 | /// Run an arbitrary closure on the shared worker thread. 545 | /// 546 | /// # Panics 547 | /// This panics if the provided closure fails or the worker thread is not 548 | /// running, which will happen if a previous operation panicked. 549 | pub fn run(f: F) -> R 550 | where 551 | R: Send, 552 | F: FnOnce(BaggageHandler) -> R, 553 | F: Send, 554 | { 555 | try_run(f).unwrap() 556 | } 557 | 558 | /// Run an arbitrary closure on the shared worker thread, similar to the 559 | /// [`run()`][Self::run()] method. However, this does _not_ panic if the 560 | /// worker thread has stopped unlike [`run`]. 561 | /// 562 | /// # Errors 563 | /// This will throw an error if the operation fails, usually due to an issue 564 | /// with the worker thread. See the [`Error`] type for more details. 565 | fn try_run(f: F) -> Result 566 | where 567 | R: Send, 568 | F: FnOnce(BaggageHandler) -> R, 569 | F: Send, 570 | { 571 | let (task_sender, worker) = &*THREAD_SENDER; 572 | 573 | if std::thread::current().id() == *worker { 574 | // If the current thread is the worker thread then run the closure. 575 | // This (mostly) prevents a deadlock where a closure on the worker 576 | // thread is waiting for another operation in the worker threads queue. 577 | 578 | // Safety: we are on the worker thread. 579 | let baggage_handler = unsafe { BaggageHandler::new() }; 580 | return Ok(f(baggage_handler)); 581 | } 582 | 583 | // Set up a rendezvous channel so that the closure can return the value. 584 | let (sender, receiver) = bounded(0); 585 | // This is the closure that will get run on the worker thread, it gets 586 | // boxed up as we're passing ownership to that thread and we can't pass 587 | // it directly due to every invocation of this function potentially 588 | // having a different closure type. 589 | let closure = Box::new(move || { 590 | let baggage_handler = unsafe { BaggageHandler::new() }; 591 | let value = f(baggage_handler); 592 | let _ = sender.send(value); 593 | // Note that after calling `send` we have dropped or returned all values 594 | // that could possibly be holding references to data on the calling 595 | // thread, which makes it safe for `try_run` to return. 596 | }) as Box; 597 | 598 | // Extend the closure's lifetime as rust doesn't know that we won't return 599 | // until the closure has given all possible references back to us. 600 | // Safety: 601 | // We have to be careful with this closure from now on but we know that 602 | // anything borrowed by the closure must live at least as long as this 603 | // function call. So we must be careful that this closure is dropped before 604 | // this function returns and `result` is dropped. 605 | let closure: Box = unsafe { mem::transmute(closure) }; 606 | 607 | // Send the closure to the worker thread! 608 | // `task_sender` is an unbounded channel so this shouldn't block but 609 | // it may fail if the worker thread has stopped. In that case the 610 | // message gets given back to us and immediately dropped, satisfying the 611 | // closure safety conditions. 612 | task_sender.send(closure).map_err(|_| Error::Send)?; 613 | // The closure is now running/pending on the worker thread. It will notify 614 | // us when it's done and in the meantime we must keep everything alive. 615 | // Note that `recv` can fail, but only in the case where the channel gets 616 | // disconnected and the only way that can happen is if the worker thread 617 | // drops the message, so we're safe to exit. 618 | receiver.recv().map_err(|_| Error::Recv) 619 | } 620 | 621 | /// The error type used by [`DiplomaticBag::try_run()`]. 622 | /// 623 | /// This indicates that the underlying worker thread is not running, this is 624 | /// probably because a user provided closure panicked and crashed the thread. 625 | /// 626 | /// [`DiplomaticBag::try_run()`]: DiplomaticBag::try_run() 627 | #[derive(Debug)] 628 | enum Error { 629 | /// An issue occurred with sending the closure to the worker thread. 630 | /// This would usually indicate that the worker thread has stopped for some 631 | /// reason (presumably a user provided closure panicked). 632 | Send, 633 | /// An issue occurred while waiting for the worker thread to send the 634 | /// notification back, either the closure panicked or something in the 635 | /// queue before us panicked. 636 | Recv, 637 | } 638 | 639 | impl std::error::Error for Error {} 640 | 641 | impl fmt::Display for Error { 642 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 643 | f.write_str("diplomatic bag worker thread not running") 644 | } 645 | } 646 | 647 | /// The code to run on the worker thread. 648 | type Message = Box; 649 | 650 | /// A wrapper type that makes it completely unsafe to access the type that it 651 | /// wraps. This is what makes the [`DiplomaticBag`] type `Send` and `Sync` even 652 | /// when `T` is not. It has similar semantics to [`ManuallyDrop`] as it just 653 | /// wraps one. 654 | #[repr(transparent)] 655 | struct Untouchable(ManuallyDrop); 656 | 657 | impl Untouchable { 658 | /// Create a new `Untouchable`. 659 | fn new(value: T) -> Self { 660 | Self(ManuallyDrop::new(value)) 661 | } 662 | 663 | /// Consume the `Untouchable` and get the wrapped type out. 664 | /// 665 | /// # Safety 666 | /// This must be called on the same thread that the type was created on if 667 | /// `T` is not `Send`. 668 | unsafe fn into_inner(self) -> T { 669 | ManuallyDrop::into_inner(self.0) 670 | } 671 | 672 | /// Get a shared reference to the wrapped type. 673 | /// 674 | /// # Safety 675 | /// This must be called on the same thread that the type was created on if 676 | /// `T` is not `Sync`. 677 | unsafe fn as_ref(&self) -> &T { 678 | &self.0 679 | } 680 | 681 | /// Get a shared reference to the wrapped type. 682 | /// 683 | /// # Safety 684 | /// This must be called on the same thread that the type was created on if 685 | /// `T` is not `Send`. 686 | unsafe fn as_mut(&mut self) -> &mut T { 687 | &mut self.0 688 | } 689 | 690 | /// Runs the drop code on the wrapped value. 691 | /// 692 | /// # Safety 693 | /// This must be called on the same thread that the type was created on if 694 | /// `T` is not `Send`. It must also only ever be called once, and the value 695 | /// inside the `Untouchable` never accessed again. Preferably, the 696 | /// `Untouchable` should be immediately dropped after calling this method. 697 | unsafe fn drop(&mut self) { 698 | unsafe { ManuallyDrop::drop(&mut self.0) }; 699 | } 700 | } 701 | 702 | // Safety: 703 | // It is unsafe to access the value inside an `Untouchable`, so it's ok for the 704 | // wrapper to be `Send` and `Sync`. 705 | unsafe impl Send for Untouchable {} 706 | unsafe impl Sync for Untouchable {} 707 | 708 | #[cfg(test)] 709 | mod tests { 710 | use slotmap::{DefaultKey, SlotMap}; 711 | use static_assertions::{assert_impl_all, assert_not_impl_any}; 712 | use std::{ 713 | cell::{Cell, RefCell}, 714 | marker::PhantomData, 715 | sync::{ 716 | atomic::{AtomicBool, Ordering}, 717 | Arc, 718 | }, 719 | }; 720 | 721 | use super::*; 722 | 723 | thread_local! { 724 | static TEST: RefCell> = RefCell::new(SlotMap::new()); 725 | } 726 | 727 | struct NotSend { 728 | key: DefaultKey, 729 | value: Cell, 730 | marker: PhantomData<*mut ()>, 731 | } 732 | impl NotSend { 733 | fn new() -> Self { 734 | let value = rand::random(); 735 | let key = TEST.with(|map| map.borrow_mut().insert(value)); 736 | Self { 737 | key, 738 | value: Cell::new(value), 739 | marker: PhantomData, 740 | } 741 | } 742 | 743 | fn change(&self) { 744 | self.value.set(rand::random()); 745 | TEST.with(|map| map.borrow_mut()[self.key] = self.value.get()) 746 | } 747 | 748 | fn verify(&self) { 749 | assert_eq!( 750 | Some(self.value.get()), 751 | TEST.with(|map| map.borrow().get(self.key).copied()) 752 | ); 753 | } 754 | } 755 | impl Drop for NotSend { 756 | fn drop(&mut self) { 757 | self.verify() 758 | } 759 | } 760 | 761 | assert_impl_all!(DiplomaticBag<*mut ()>: Send, Sync); 762 | assert_not_impl_any!(BaggageHandler: Send, Sync); 763 | assert_impl_all!(Error: std::error::Error, Send, Sync); 764 | 765 | #[test] 766 | fn create_and_drop() { 767 | let _value = DiplomaticBag::new(|_| NotSend::new()); 768 | } 769 | 770 | #[test] 771 | fn execute() { 772 | let value = DiplomaticBag::new(|_| NotSend::new()); 773 | value.map(|_, value| value.verify()); 774 | } 775 | 776 | #[test] 777 | fn execute_ref() { 778 | let value = DiplomaticBag::new(|_| NotSend::new()); 779 | value.as_ref().map(|_, value| { 780 | value.verify(); 781 | value.change(); 782 | }); 783 | } 784 | 785 | #[test] 786 | fn execute_mut() { 787 | let mut value = DiplomaticBag::new(|_| NotSend::new()); 788 | value.as_mut().map(|_, value| { 789 | value.verify(); 790 | value.change(); 791 | }); 792 | } 793 | 794 | #[test] 795 | fn drop_inner() { 796 | let atomic = Arc::new(AtomicBool::new(false)); 797 | struct SetOnDrop(Arc); 798 | impl Drop for SetOnDrop { 799 | fn drop(&mut self) { 800 | self.0.store(true, Ordering::SeqCst); 801 | } 802 | } 803 | 804 | let bag = DiplomaticBag::new(|_| SetOnDrop(atomic.clone())); 805 | assert!(!atomic.load(Ordering::SeqCst)); 806 | drop(bag); 807 | assert!(atomic.load(Ordering::SeqCst)); 808 | } 809 | 810 | #[test] 811 | fn readme_version() { 812 | version_sync::assert_markdown_deps_updated!("README.md"); 813 | } 814 | 815 | #[test] 816 | fn html_root_url_version() { 817 | version_sync::assert_html_root_url_updated!("src/lib.rs"); 818 | } 819 | } 820 | --------------------------------------------------------------------------------