state-0.6.0/.cargo_vcs_info.json0000644000000001360000000000100121570ustar { "git": { "sha1": "dc6ab620f19e5dae3e731353cbddc6d55a2b126e" }, "path_in_vcs": "" }state-0.6.0/.github/workflows/CI.yml000064400000000000000000000047341046102023000153720ustar 00000000000000name: CI on: [push, pull_request] jobs: test: name: "${{ matrix.platform.name }} ${{ matrix.test.name }} (${{ matrix.platform.toolchain }})" strategy: matrix: platform: - { name: Linux, distro: ubuntu-latest, toolchain: stable } - { name: Windows, distro: windows-latest, toolchain: stable } - { name: macOS, distro: macOS-latest, toolchain: stable } - { name: Linux, distro: ubuntu-latest, toolchain: nightly } test: - { name: Default } - { name: Featured, args: "--all-features" } runs-on: ${{ matrix.platform.distro }} steps: - name: Checkout Sources uses: actions/checkout@v2 - name: Install Rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.platform.toolchain }} override: true - name: Run Tests uses: actions-rs/cargo@v1 with: command: test args: ${{ matrix.test.args }} check-msrv: name: Check MSRV (1.61.0) runs-on: ubuntu-latest steps: - name: Checkout Sources uses: actions/checkout@v2 - name: Install Rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: 1.61.0 override: true - name: Check Compilation uses: actions-rs/cargo@v1 with: command: check args: --all --all-features loom-verify: name: Loom Verification runs-on: ubuntu-latest steps: - name: Checkout Sources uses: actions/checkout@v2 - name: Install Rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: nightly override: true - name: Run Loom Verification uses: actions-rs/cargo@v1 env: RUSTFLAGS: --cfg loom --cfg loom_nightly with: command: test args: --release --test loom miri-verify: name: Miri Verification runs-on: ubuntu-latest steps: - name: Checkout Sources uses: actions/checkout@v2 - name: Install Rust w/Miri uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: nightly components: miri override: true - name: Run Miri Verification uses: actions-rs/cargo@v1 env: MIRIFLAGS: -Zmiri-track-raw-pointers with: command: miri args: test state-0.6.0/.gitignore000064400000000000000000000000271046102023000127360ustar 00000000000000target Cargo.lock main state-0.6.0/Cargo.toml0000644000000022750000000000100101630ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "state" version = "0.6.0" authors = ["Sergio Benitez "] description = """ A library for safe and effortless global and thread-local state management. """ documentation = "https://docs.rs/state/0.6.0" readme = "README.md" keywords = [ "static", "tls", "state", "thread-local", "global", ] license = "MIT/Apache-2.0" repository = "https://github.com/SergioBenitez/state" [package.metadata.docs.rs] all-features = true [profile.bench] opt-level = 3 lto = true [profile.release] opt-level = 3 lto = true [dependencies] [dev-dependencies.static_assertions] version = "1.1.0" [features] tls = [] [target."cfg(loom)".dependencies.loom] version = "0.5" features = ["checkpoint"] state-0.6.0/Cargo.toml.orig000064400000000000000000000013221046102023000136340ustar 00000000000000[package] name = "state" version = "0.6.0" authors = ["Sergio Benitez "] description = """ A library for safe and effortless global and thread-local state management. """ documentation = "https://docs.rs/state/0.6.0" repository = "https://github.com/SergioBenitez/state" readme = "README.md" keywords = ["static", "tls", "state", "thread-local", "global"] license = "MIT/Apache-2.0" edition = "2021" [features] tls = [] [dependencies] [dev-dependencies] static_assertions = "1.1.0" [target.'cfg(loom)'.dependencies] loom = { version = "0.5", features = ["checkpoint"] } [profile.release] opt-level = 3 lto = true [profile.bench] opt-level = 3 lto = true [package.metadata.docs.rs] all-features = true state-0.6.0/LICENSE-APACHE000064400000000000000000000251371046102023000127030ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. state-0.6.0/LICENSE-MIT000064400000000000000000000020701046102023000124020ustar 00000000000000The MIT License (MIT) Copyright (c) 2016 Sergio Benitez Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. state-0.6.0/README.md000064400000000000000000000056221046102023000122330ustar 00000000000000# `state`   [![ci.svg]][ci] [![crates.io]][crate] [![docs.rs]][docs] [crates.io]: https://img.shields.io/crates/v/state.svg [crate]: https://crates.io/crates/state [docs.rs]: https://docs.rs/state/badge.svg [docs]: https://docs.rs/state [ci.svg]: https://github.com/SergioBenitez/state/workflows/CI/badge.svg [ci]: https://github.com/SergioBenitez/state/actions A Rust library for safe and effortless global and thread-local state management. ```rust extern crate state; static GLOBAL: state::InitCell = state::InitCell::new(); GLOBAL.set(42); assert_eq!(*GLOBAL.get(), 42); ``` This library can be used to easily implement: * Lazier Global Statics * Singletons, Init-Once Values * Global or Thread-Local Caches, Thunks * Dynamically-Initialized Thread-Local Data * Type Maps, Type-Based TypeMaps See the [documentation](https://docs.rs/state) for more. ## Usage Include `state` in your `Cargo.toml` `[dependencies]`: ```toml [dependencies] state = "0.6.0" ``` Thread-local state management is not enabled by default. You can enable it via the `tls` feature: ```toml [dependencies] state = { version = "0.6.0", features = ["tls"] } ``` ## MSRV The minimum supported Rust version is `1.61.0` as of `state` version `0.6`. ## Correctness `state` has been extensively vetted, manually and automatically, for soundness and correctness. _All_ unsafe code, including in internal concurrency primitives, `TypeMap`, and `InitCell` are exhaustively verified for pairwise concurrency correctness and internal aliasing exclusion with `loom`. Multithreading invariants, aliasing invariants, and other soundness properties are verified with `miri`. Verification is run by the CI on every commit. ## Performance `state` is heavily tuned to perform optimally. `InitCell` is optimal for global storage retrieval; it is _slightly faster_ than accessing global state initialized through `lazy_static!`, more so across many threads. `LocalInitCell` incurs slight overhead due to thread lookup. However, `LocalInitCell` has no synchronization overhead, so retrieval from `LocalInitCell` is faster than through `InitCell` across many threads. Bear in mind that `state` allows global initialization at _any_ point in the program. Other solutions, such as `lazy_static!` and `thread_local!` allow initialization _only_ a priori. In other words, `state`'s abilities are a superset of those provided by `lazy_static!` and `thread_local!` while being more performant. ## Testing Tests can be found in the `tests` directory. You can run tests with `cargo test --all-features`. Loom verification can be run with `RUSTFLAGS="--cfg loom" cargo test --release --test loom`. ## License `state` is licensed under either of the following, at your option: * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT License ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) state-0.6.0/src/cell.rs000064400000000000000000000231541046102023000130300ustar 00000000000000use std::fmt; use crate::shim::cell::UnsafeCell; use crate::init::Init; /// An init-once cell for global access to a value. /// /// A `InitCell` instance can hold a single value in a global context. A /// `InitCell` instance begins without a value and must be initialized via the /// [`set()`](#method.set) method. Once a value has been set, it can be /// retrieved at any time and in any thread via the [`get()`](#method.get) /// method. The [`try_get()`](#method.try_get) can be used to determine whether /// the `InitCell` has been initialized before attempting to retrieve the value. /// /// # Example /// /// The following example uses `InitCell` to hold a global instance of a /// `HashMap` which can be modified at will: /// /// ```rust /// use std::collections::HashMap; /// use std::sync::Mutex; /// use std::thread; /// /// use state::InitCell; /// /// static GLOBAL_MAP: InitCell>> = InitCell::new(); /// /// fn run_program() { /// let mut map = GLOBAL_MAP.get().lock().unwrap(); /// map.insert("another_key".into(), "another_value".into()); /// } /// /// fn main() { /// // Create the initial map and store it in `GLOBAL_MAP`. /// let mut initial_map = HashMap::new(); /// initial_map.insert("key".into(), "value".into()); /// GLOBAL_MAP.set(Mutex::new(initial_map)); /// /// // For illustration, we spawn a new thread that modified the map. /// thread::spawn(|| run_program()).join().expect("thread"); /// /// // Assert that the modification took place. /// let map = GLOBAL_MAP.get().lock().unwrap(); /// assert_eq!(map.get("another_key").unwrap(), "another_value"); /// } pub struct InitCell { item: UnsafeCell>, init: Init } /// Defaults to [`InitCell::new()`]. impl Default for InitCell { fn default() -> Self { InitCell::new() } } impl InitCell { /// Create a new, uninitialized cell. /// /// To create a cell initializd with a value, use [`InitCell::from()`]. /// /// # Example /// /// ```rust /// use state::InitCell; /// /// static MY_GLOBAL: InitCell = InitCell::new(); /// ``` #[cfg(not(loom))] pub const fn new() -> InitCell { InitCell { item: UnsafeCell::new(None), init: Init::new() } } /// New, for loom. #[cfg(loom)] pub fn new() -> InitCell { InitCell { item: UnsafeCell::new(None), init: Init::new() } } /// Sets this cell's value to `value` if it is not already initialized. /// /// If there are multiple simultaneous callers, exactly one is guaranteed to /// receive `true`, indicating its value was set. All other callers receive /// `false`, indicating the value was ignored. /// /// # Example /// /// ```rust /// # use state::InitCell; /// static MY_GLOBAL: InitCell<&'static str> = InitCell::new(); /// /// assert_eq!(MY_GLOBAL.set("Hello, world!"), true); /// assert_eq!(MY_GLOBAL.set("Goodbye, world!"), false); /// ``` pub fn set(&self, value: T) -> bool { if self.init.needed() { unsafe { self.item.with_mut(|ptr| *ptr = Some(value)); } self.init.mark_complete(); return true; } false } /// Resets the cell to an uninitialized state. /// /// # Example /// /// ```rust /// use state::InitCell; /// /// let mut cell = InitCell::from(5); /// assert_eq!(cell.get(), &5); /// /// cell.reset(); /// assert!(cell.try_get().is_none()); /// ``` pub fn reset(&mut self) { *self = Self::new(); } /// If the cell is not initialized, it is set `f()`. Returns a borrow to the /// value in this cell. /// /// If `f()` panics during initialization, the cell is left uninitialized. /// /// # Example /// /// ```rust /// # use state::InitCell; /// static MY_GLOBAL: InitCell<&'static str> = InitCell::new(); /// /// assert_eq!(*MY_GLOBAL.get_or_init(|| "Hello, world!"), "Hello, world!"); /// ``` #[inline] pub fn get_or_init T>(&self, f: F) -> &T { if let Some(value) = self.try_get() { value } else { self.set(f()); self.try_get().expect("cell::get_or_init(): set() => get() ok") } } /// Waits (blocks) until the cell has a value and then borrows it. /// /// # Example /// /// ```rust /// # use state::InitCell; /// static MY_GLOBAL: InitCell<&'static str> = InitCell::new(); /// /// MY_GLOBAL.set("Hello, world!"); /// assert_eq!(*MY_GLOBAL.get(), "Hello, world!"); /// ``` #[inline] pub fn wait(&self) -> &T { self.init.wait_until_complete(); self.try_get().expect("cell::wait(): broken (init await complete w/o value)") } /// Get a reference to the underlying value, if one is set. /// /// Returns `Some` if the state has previously been set via methods like /// [`InitCell::set()`] or [`InitCell::get_or_init()`]. Otherwise returns /// `None`. /// /// # Example /// /// ```rust /// # use state::InitCell; /// static MY_GLOBAL: InitCell<&'static str> = InitCell::new(); /// /// assert_eq!(MY_GLOBAL.try_get(), None); /// /// MY_GLOBAL.set("Hello, world!"); /// /// assert_eq!(MY_GLOBAL.try_get(), Some(&"Hello, world!")); /// ``` #[inline] pub fn try_get(&self) -> Option<&T> { if self.init.has_completed() { unsafe { self.item.with(|ptr| (*ptr).as_ref()) } } else { None } } /// Returns a mutable reference to the underlying data if any is set. /// /// This call borrows `InitCell` mutably (at compile-time) so there is no /// need for dynamic checks. /// /// # Example /// /// ```rust /// use state::InitCell; /// /// let mut cell = InitCell::from(5); /// *cell.try_get_mut().unwrap() += 1; /// /// let mut cell: InitCell = InitCell::new(); /// assert!(cell.try_get_mut().is_none()); /// ``` pub fn try_get_mut(&mut self) -> Option<&mut T> { self.item.get_mut().as_mut() } /// Borrows the value in this cell, panicking if there is no value. /// /// # Panics /// /// Panics if a value has not previously been [`set()`](#method.set). Use /// [`try_get()`](#method.try_get) for a non-panicking version. /// /// # Example /// /// ```rust /// # use state::InitCell; /// static MY_GLOBAL: InitCell<&'static str> = InitCell::new(); /// /// MY_GLOBAL.set("Hello, world!"); /// assert_eq!(*MY_GLOBAL.get(), "Hello, world!"); /// ``` #[inline] pub fn get(&self) -> &T { self.try_get().expect("cell::get(): called get() before set()") } /// Resets the cell to an uninitialized state and returns the inner value if /// any was set. /// /// # Example /// /// ```rust /// use state::InitCell; /// /// let mut cell = InitCell::from(5); /// assert_eq!(cell.get(), &5); /// assert_eq!(cell.get(), &5); /// /// assert_eq!(cell.take(), Some(5)); /// assert_eq!(cell.take(), None); /// ``` pub fn take(&mut self) -> Option { std::mem::replace(self, Self::new()).into_inner() } /// Returns the inner value if any is set. /// /// # Example /// /// ```rust /// use state::InitCell; /// /// let cell = InitCell::from(5); /// assert_eq!(cell.into_inner().unwrap(), 5); /// /// let cell: InitCell = InitCell::new(); /// assert!(cell.into_inner().is_none()); /// ``` pub fn into_inner(self) -> Option { self.item.into_inner() } /// Applies the function `f` to the inner value, if there is any, leaving /// the updated value in the cell. /// /// If `f()` panics during updating, the cell is left uninitialized. /// /// # Example /// /// ```rust /// use state::InitCell; /// /// let mut cell = InitCell::from(5); /// cell.update(|v| v + 10); /// assert_eq!(cell.wait(), &15); /// /// let mut cell = InitCell::new(); /// cell.update(|v: u8| v + 10); /// assert!(cell.try_get().is_none()); /// ``` pub fn update T>(&mut self, f: F) { self.take().map(|v| self.set(f(v))); } /// Applies the function `f` to the inner value, if there is any, and /// returns a new `InitCell` with mapped value. /// /// # Example /// /// ```rust /// use state::InitCell; /// /// let cell = InitCell::from(5); /// assert_eq!(cell.get(), &5); /// /// let cell = cell.map(|v| v + 10); /// assert_eq!(cell.get(), &15); /// ``` pub fn map U>(self, f: F) -> InitCell { self.into_inner().map_or_else(InitCell::new, |v| InitCell::from(f(v))) } } unsafe impl Send for InitCell { } unsafe impl Sync for InitCell { } impl fmt::Debug for InitCell { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self.try_get() { Some(object) => object.fmt(f), None => write!(f, "[uninitialized cell]") } } } impl From for InitCell { fn from(value: T) -> InitCell { let cell = InitCell::new(); assert!(cell.set(value)); cell } } impl Clone for InitCell { fn clone(&self) -> InitCell { match self.try_get() { Some(val) => InitCell::from(val.clone()), None => InitCell::new() } } } state-0.6.0/src/ident_hash.rs000064400000000000000000000012721046102023000142140ustar 00000000000000use std::hash::Hasher; // This is a _super_ stupid hash. It just uses its input as the hash value. This // hash is meant to be used _only_ for "prehashed" values. In particular, we use // this so that hashing a TypeId is essentially a noop. This is because TypeIds // are already unique integers. #[derive(Default)] pub struct IdentHash(u64); impl Hasher for IdentHash { fn finish(&self) -> u64 { self.0 } fn write(&mut self, bytes: &[u8]) { for byte in bytes { self.write_u8(*byte); } } fn write_u8(&mut self, i: u8) { self.0 = (self.0 << 8) | (i as u64); } fn write_u64(&mut self, i: u64) { self.0 = i; } } state-0.6.0/src/init.rs000064400000000000000000000066071046102023000130600ustar 00000000000000use crate::shim::sync::atomic::{AtomicBool, Ordering::{AcqRel, Acquire, Release, Relaxed}}; use crate::shim::thread::yield_now; /// An atomic initializer: mutual exclusion during initialization. pub struct Init { started: AtomicBool, done: AtomicBool } impl Init { /// A ready-to-init initializer. #[cfg(not(loom))] pub const fn new() -> Init { Init { started: AtomicBool::new(false), done: AtomicBool::new(false) } } /// A ready-to-init initializer. #[cfg(loom)] pub fn new() -> Init { Init { started: AtomicBool::new(false), done: AtomicBool::new(false) } } /// Returns true if initialization has completed without blocking. If this /// function returns false, it may be the case that initialization is /// currently in progress. If this function returns `true`, intialization is /// guaranteed to be completed. #[inline(always)] pub fn has_completed(&self) -> bool { self.done.load(Acquire) } /// Mark this initialization as complete, unblocking all threads that may be /// waiting. Only the caller that received `true` from `needed()` is /// expected to call this method. #[inline(always)] pub fn mark_complete(&self) { // If this is being called from outside of a `needed` block, we need to // ensure that `started` is `true` to avoid racing with (return `true` // to) future `needed` calls. self.started.store(true, Release); self.done.store(true, Release); } /// Blocks until initialization is marked as completed. /// /// // NOTE: Internally, this waits for the the done flag. #[inline(always)] pub fn wait_until_complete(&self) { while !self.done.load(Acquire) { yield_now() } } #[cold] #[inline(always)] fn try_to_need_init(&self) -> bool { // Quickly check if initialization has already started elsewhere. if self.started.load(Relaxed) { // If it has, wait until it's finished before returning. Finishing // is marked by calling `mark_complete`. self.wait_until_complete(); return false; } // Try to be the first. If we lose (init_started is true), we wait. if self.started.compare_exchange(false, true, AcqRel, Relaxed).is_err() { // Another compare_and_swap won. Wait until they're done. self.wait_until_complete(); return false; } true } /// If initialization is complete, returns `false`. Otherwise, returns /// `true` exactly once, to the caller that should perform initialization. /// All other calls block until initialization is marked completed, at which /// point `false` is returned. /// /// If this function is called from multiple threads simulatenously, exactly /// one thread is guaranteed to receive `true`. All other threads are /// blocked until initialization is marked completed. #[inline(always)] pub fn needed(&self) -> bool { // Quickly check if initialization has finished, and return if so. if self.has_completed() { return false; } // We call a different function to attempt the intialiaztion to use // Rust's `cold` attribute to try let LLVM know that this is unlikely. self.try_to_need_init() } } state-0.6.0/src/lib.rs000064400000000000000000000212271046102023000126560ustar 00000000000000#![doc(html_root_url = "https://docs.rs/state/0.6.0")] #![warn(missing_docs)] //! # Safe, Effortless `state` Management //! //! This crate allows you to safely and effortlessly manage global and/or //! thread-local state. Three primitives are provided for state management: //! //! * **[`struct@TypeMap`]:** Type-based storage for many values. //! * **[`InitCell`]:** Thread-safe init-once storage for a single value. //! * **[`LocalInitCell`]:** Thread-local init-once-per-thread cell. //! //! ## Usage //! //! Include `state` in your `Cargo.toml` `[dependencies]`: //! //! ```toml //! [dependencies] //! state = "0.6.0" //! ``` //! //! Thread-local state management is not enabled by default. You can enable it //! via the `tls` feature: //! //! ```toml //! [dependencies] //! state = { version = "0.6.0", features = ["tls"] } //! ``` //! //! ## Use Cases //! //! ### Memoizing Expensive Operations //! //! The [`InitCell`] type can be used to conveniently memoize expensive //! read-based operations without needing to mutably borrow. Consider a `struct` //! with a field `value` and method `compute()` that performs an expensive //! operation on `value` to produce a derived value. We can use `InitCell` to //! memoize `compute()`: //! //! ```rust //! use state::InitCell; //! //! struct Value; //! struct DerivedValue; //! //! struct Foo { //! value: Value, //! cached: InitCell //! } //! //! impl Foo { //! fn set_value(&mut self, v: Value) { //! self.value = v; //! self.cached.reset(); //! } //! //! fn compute(&self) -> &DerivedValue { //! self.cached.get_or_init(|| { //! let _value = &self.value; //! unimplemented!("expensive computation with `self.value`") //! }) //! } //! } //! ``` //! //! ### Read-Only Singleton //! //! Suppose you have the following structure which is initialized in `main` //! after receiving input from the user: //! //! ```rust //! struct Configuration { //! name: String, //! number: isize, //! verbose: bool //! } //! //! fn main() { //! let config = Configuration { //! /* fill in structure at run-time from user input */ //! # name: "Sergio".to_string(), //! # number: 1, //! # verbose: true //! }; //! } //! ``` //! //! You'd like to access this structure later, at any point in the program, //! without any synchronization overhead. Prior to `state`, assuming you needed //! to setup the structure after program start, your options were: //! //! 1. Use `static mut` and `unsafe` to set an `Option` to //! `Some`. Retrieve by checking for `Some`. //! 2. Use `lazy_static` with a `RwLock` to set an //! `RwLock>` to `Some`. Retrieve by `lock`ing and //! checking for `Some`, paying the cost of synchronization. //! //! With `state`, you can use [`LocalInitCell`] as follows: //! //! ```rust //! # extern crate state; //! # #[cfg(feature = "tls")] //! # fn main() { //! # use state::LocalInitCell; //! # struct Configuration { name: String, number: isize, verbose: bool } //! static CONFIG: LocalInitCell = LocalInitCell::new(); //! //! fn main() { //! CONFIG.set(|| Configuration { //! /* fill in structure at run-time from user input */ //! # name: "Sergio".to_string(), //! # number: 1, //! # verbose: true //! }); //! //! /* at any point later in the program, in any thread */ //! let config = CONFIG.get(); //! } //! # } //! # #[cfg(not(feature = "tls"))] //! # fn main() { } //! ``` //! //! Note that you can _also_ use [`InitCell`] to the same effect. //! //! ### Read/Write Singleton //! //! Following from the previous example, let's now say that we want to be able //! to modify our singleton `Configuration` structure as the program evolves. We //! have two options: //! //! 1. If we want to maintain the _same_ state in any thread, we can use a //! `InitCell` structure and wrap our `Configuration` structure in a //! synchronization primitive. //! 2. If we want to maintain _different_ state in any thread, we can continue //! to use a `LocalInitCell` structure and wrap our `LocalInitCell` type in a //! `Cell` structure for internal mutability. //! //! In this example, we'll choose **1**. The next example illustrates an //! instance of **2**. //! //! The following implements **1** by using a `InitCell` structure and wrapping //! the `Configuration` type with a `RwLock`: //! //! ```rust //! # struct Configuration { name: String, number: isize, verbose: bool } //! # use state::InitCell; //! # use std::sync::RwLock; //! static CONFIG: InitCell> = InitCell::new(); //! //! fn main() { //! let config = Configuration { //! /* fill in structure at run-time from user input */ //! # name: "Sergio".to_string(), //! # number: 1, //! # verbose: true //! }; //! //! // Make the config avaiable globally. //! CONFIG.set(RwLock::new(config)); //! //! /* at any point later in the program, in any thread */ //! let mut_config = CONFIG.get().write(); //! } //! ``` //! //! ### Mutable, thread-local data //! //! Imagine you want to count the number of invocations to a function per //! thread. You'd like to store the count in a `Cell` and use //! `count.set(count.get() + 1)` to increment the count. Prior to `state`, your //! only option was to use the `thread_local!` macro. `state` provides a more //! flexible, and arguably simpler solution via `LocalInitCell`. This scanario //! is implemented in the folloiwng: //! //! ```rust //! # extern crate state; //! # use std::cell::Cell; //! # use std::thread; //! # #[cfg(feature = "tls")] //! # use state::LocalInitCell; //! # #[cfg(feature = "tls")] //! static COUNT: LocalInitCell> = LocalInitCell::new(); //! //! # #[cfg(not(feature = "tls"))] fn function_to_measure() { } //! # #[cfg(feature = "tls")] //! fn function_to_measure() { //! let count = COUNT.get(); //! count.set(count.get() + 1); //! } //! //! # #[cfg(not(feature = "tls"))] fn main() { } //! # #[cfg(feature = "tls")] //! fn main() { //! // setup the initializer for thread-local state //! COUNT.set(|| Cell::new(0)); //! //! // spin up many threads that call `function_to_measure`. //! let mut threads = vec![]; //! for i in 0..10 { //! threads.push(thread::spawn(|| { //! // Thread IDs may be reusued, so we reset the state. //! COUNT.get().set(0); //! function_to_measure(); //! COUNT.get().get() //! })); //! } //! //! // retrieve the total //! let total: usize = threads.into_iter() //! .map(|t| t.join().unwrap()) //! .sum(); //! //! assert_eq!(total, 10); //! } //! ``` //! ## Correctness //! //! `state` has been extensively vetted, manually and automatically, for soundness //! and correctness. _All_ unsafe code, including in internal concurrency //! primitives, `TypeMap`, and `InitCell` are exhaustively verified for pairwise //! concurrency correctness and internal aliasing exclusion with `loom`. //! Multithreading invariants, aliasing invariants, and other soundness properties //! are verified with `miri`. Verification is run by the CI on every commit. //! //! ## Performance //! //! `state` is heavily tuned to perform optimally. `get{_local}` and //! `set{_local}` calls to a `TypeMap` incur overhead due to type lookup. //! `InitCell`, on the other hand, is optimal for global storage retrieval; it is //! _slightly faster_ than accessing global state initialized through //! `lazy_static!`, more so across many threads. `LocalInitCell` incurs slight //! overhead due to thread lookup. However, `LocalInitCell` has no //! synchronization overhead, so retrieval from `LocalInitCell` is faster than //! through `InitCell` across many threads. //! //! Bear in mind that `state` allows global initialization at _any_ point in the //! program. Other solutions, such as `lazy_static!` and `thread_local!` allow //! initialization _only_ a priori. In other words, `state`'s abilities are a //! superset of those provided by `lazy_static!` and `thread_local!` while being //! more performant. //! //! ## When To Use //! //! You should avoid using global `state` as much as possible. Instead, thread //! state manually throughout your program when feasible. mod ident_hash; mod cell; mod init; mod shim; #[doc(hidden)] pub mod type_map; pub use type_map::TypeMap; pub use cell::InitCell; #[cfg(feature = "tls")] mod tls; #[cfg(feature = "tls")] mod thread_local; #[cfg(feature = "tls")] pub use tls::LocalInitCell; /// Exports for use by loom tests but otherwise private. #[cfg(loom)] #[path = "."] pub mod private { /// The `Init` type. pub mod init; } state-0.6.0/src/shim.rs000064400000000000000000000040221046102023000130420ustar 00000000000000#[cfg(not(loom))] pub use std::sync; #[cfg(loom)] pub use loom::sync; pub mod cell { #[cfg(not(loom))] type Inner = std::cell::UnsafeCell; #[cfg(loom)] type Inner = loom::cell::UnsafeCell; #[derive(Debug)] pub struct UnsafeCell(Inner); impl UnsafeCell { #[cfg(not(loom))] #[inline(always)] pub const fn new(data: T) -> UnsafeCell { UnsafeCell(Inner::new(data)) } #[cfg(loom)] #[cfg_attr(loom_nightly, track_caller)] pub fn new(data: T) -> UnsafeCell { UnsafeCell(Inner::new(data)) } #[inline(always)] #[cfg_attr(loom_nightly, track_caller)] pub fn with(&self, f: impl FnOnce(*const T) -> R) -> R { #[cfg(not(loom))] { f(self.0.get()) } #[cfg(loom)] { self.0.with(f) } } #[inline(always)] #[cfg_attr(loom_nightly, track_caller)] pub fn with_mut(&self, f: impl FnOnce(*mut T) -> R) -> R { #[cfg(not(loom))] { f(self.0.get()) } #[cfg(loom)] { self.0.with_mut(f) } } #[inline(always)] #[cfg_attr(loom_nightly, track_caller)] pub fn get_mut(&mut self) -> &mut T { // SAFETY: This is the fully safe `UnsafeCell::get_mut()` introduced // in Rust 1.50.0. We don't use it to keep the MSRV down. #[cfg(not(loom))] unsafe { &mut *self.0.get() } #[cfg(loom)] { self.with_mut(|ptr| unsafe { &mut *ptr }) } } #[inline(always)] #[cfg_attr(loom_nightly, track_caller)] pub fn into_inner(self) -> T { #[cfg(not(loom))] { self.0.into_inner() } #[cfg(loom)] { let value = self.with(|ptr| unsafe { std::ptr::read(ptr) }); std::mem::forget(self); value } } } } #[cfg(loom)] pub use loom::thread_local; #[cfg(not(loom))] pub use std::thread_local; #[cfg(loom)] pub use loom::thread; #[cfg(not(loom))] pub use std::thread; state-0.6.0/src/thread_local/LICENSE-APACHE000064400000000000000000000251371046102023000161130ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. state-0.6.0/src/thread_local/LICENSE-MIT000064400000000000000000000020571046102023000156170ustar 00000000000000Copyright (c) 2016 The Rust Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. state-0.6.0/src/thread_local/mod.rs000064400000000000000000000226671046102023000153210ustar 00000000000000// Copyright 2017 Amanieu d'Antras // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Per-object thread-local storage //! //! This library provides the `ThreadLocal` type which allows a separate copy of //! an object to be used for each thread. This allows for per-object //! thread-local storage, unlike the standard library's `thread_local!` macro //! which only allows static thread-local storage. //! //! Per-thread objects are not destroyed when a thread exits. Instead, objects //! are only destroyed when the `ThreadLocal` containing them is destroyed. //! //! You can also iterate over the thread-local values of all thread in a //! `ThreadLocal` object using the `iter_mut` and `into_iter` methods. This can //! only be done if you have mutable access to the `ThreadLocal` object, which //! guarantees that you are the only thread currently accessing it. //! //! A `CachedThreadLocal` type is also provided which wraps a `ThreadLocal` but //! also uses a special fast path for the first thread that writes into it. The //! fast path has very low overhead (<1ns per access) while keeping the same //! performance as `ThreadLocal` for other threads. //! //! Note that since thread IDs are recycled when a thread exits, it is possible //! for one thread to retrieve the object of another thread. Since this can only //! occur after a thread has exited this does not lead to any race conditions. #![warn(missing_docs)] mod thread_id; mod unreachable; use std::cell::UnsafeCell; use std::fmt; use std::panic::UnwindSafe; use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; use std::sync::Mutex; use self::unreachable::{UncheckedOptionExt, UncheckedResultExt}; /// Thread-local variable wrapper /// /// See the [module-level documentation](index.html) for more. pub struct ThreadLocal { // Pointer to the current top-level hash table table: AtomicPtr>, // Lock used to guard against concurrent modifications. This is only taken // while writing to the table, not when reading from it. This also guards // the counter for the total number of values in the hash table. lock: Mutex, } struct Table { // Hash entries for the table entries: Box<[TableEntry]>, // Number of bits used for the hash function hash_bits: usize, // Previous table, half the size of the current one prev: Option>>, } struct TableEntry { // Current owner of this entry, or 0 if this is an empty entry owner: AtomicUsize, // The object associated with this entry. This is only ever accessed by the // owner of the entry. data: UnsafeCell>>, } // ThreadLocal is always Sync, even if T isn't unsafe impl Sync for ThreadLocal {} impl Default for ThreadLocal { fn default() -> ThreadLocal { ThreadLocal::new() } } impl Drop for ThreadLocal { fn drop(&mut self) { unsafe { let _ = Box::from_raw(self.table.load(Ordering::Relaxed)); } } } // Implementation of Clone for TableEntry, needed to make vec![] work impl Clone for TableEntry { fn clone(&self) -> TableEntry { TableEntry { owner: AtomicUsize::new(0), data: UnsafeCell::new(None), } } } // Hash function for the thread id #[cfg(target_pointer_width = "32")] #[inline] fn hash(id: usize, bits: usize) -> usize { id.wrapping_mul(0x9E3779B9) >> (32 - bits) } #[cfg(target_pointer_width = "64")] #[inline] fn hash(id: usize, bits: usize) -> usize { id.wrapping_mul(0x9E37_79B9_7F4A_7C15) >> (64 - bits) } impl ThreadLocal { /// Creates a new empty `ThreadLocal`. pub fn new() -> ThreadLocal { let entry = TableEntry { owner: AtomicUsize::new(0), data: UnsafeCell::new(None), }; let table = Table { entries: vec![entry; 2].into_boxed_slice(), hash_bits: 1, prev: None, }; ThreadLocal { table: AtomicPtr::new(Box::into_raw(Box::new(table))), lock: Mutex::new(0), } } /// Returns the element for the current thread, if it exists. pub fn get(&self) -> Option<&T> { thread_id::get() .and_then(|id| self.get_fast(id)) } /// Returns the element for the current thread, or creates it if it doesn't /// exist. pub fn get_or(&self, create: F) -> &T where F: FnOnce() -> T, { unsafe { self.get_or_try(|| Ok::(create())) .unwrap() .unchecked_unwrap_ok() } } /// Returns the element for the current thread, or creates it if it doesn't /// exist. If `create` fails, that error is returned and no element is /// added. pub fn get_or_try(&self, create: F) -> Option> where F: FnOnce() -> Result, { thread_id::get().and_then(|id| match self.get_fast(id) { Some(x) => Some(Ok(x)), None => Some( create().and_then(|obj| Ok(self.insert(id, Box::new(obj), true)) ) ), } ) } // Simple hash table lookup function fn lookup(id: usize, table: &Table) -> Option<&UnsafeCell>>> { // Because we use a Mutex to prevent concurrent modifications (but not // reads) of the hash table, we can avoid any memory barriers here. No // elements between our hash bucket and our value can have been modified // since we inserted our thread-local value into the table. for entry in table.entries.iter().cycle().skip(hash(id, table.hash_bits)) { let owner = entry.owner.load(Ordering::Relaxed); if owner == id { return Some(&entry.data); } if owner == 0 { return None; } } unreachable!(); } // Fast path: try to find our thread in the top-level hash table fn get_fast(&self, id: usize) -> Option<&T> { let table = unsafe { &*self.table.load(Ordering::Acquire) }; match Self::lookup(id, table) { Some(x) => unsafe { Some((*x.get()).as_ref().unchecked_unwrap()) }, None => self.get_slow(id, table), } } // Slow path: try to find our thread in the other hash tables, and then // move it to the top-level hash table. #[cold] fn get_slow(&self, id: usize, table_top: &Table) -> Option<&T> { let mut current = &table_top.prev; while let Some(ref table) = *current { if let Some(x) = Self::lookup(id, table) { let data = unsafe { (*x.get()).take().unchecked_unwrap() }; return Some(self.insert(id, data, false)); } current = &table.prev; } None } #[cold] fn insert(&self, id: usize, data: Box, new: bool) -> &T { // Lock the Mutex to ensure only a single thread is modify the hash // table at once. let mut count = self.lock.lock().unwrap(); if new { *count += 1; } let table_raw = self.table.load(Ordering::Relaxed); let table = unsafe { &*table_raw }; // If the current top-level hash table is more than 75% full, add a new // level with 2x the capacity. Elements will be moved up to the new top // level table as they are accessed. let table = if *count > table.entries.len() * 3 / 4 { let entry = TableEntry { owner: AtomicUsize::new(0), data: UnsafeCell::new(None), }; let new_table = Box::into_raw(Box::new(Table { entries: vec![entry; table.entries.len() * 2].into_boxed_slice(), hash_bits: table.hash_bits + 1, prev: unsafe { Some(Box::from_raw(table_raw)) }, })); self.table.store(new_table, Ordering::Release); unsafe { &*new_table } } else { table }; // Insert the new element into the top-level hash table for entry in table.entries.iter().cycle().skip(hash(id, table.hash_bits)) { let owner = entry.owner.load(Ordering::Relaxed); if owner == 0 { unsafe { entry.owner.store(id, Ordering::Relaxed); *entry.data.get() = Some(data); return (*entry.data.get()).as_ref().unchecked_unwrap(); } } if owner == id { // This can happen if create() inserted a value into this // ThreadLocal between our calls to get_fast() and insert(). We // just return the existing value and drop the newly-allocated // Box. unsafe { return (*entry.data.get()).as_ref().unchecked_unwrap(); } } } unreachable!(); } } impl fmt::Debug for ThreadLocal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ThreadLocal {{ local_data: {:?} }}", self.get()) } } impl UnwindSafe for ThreadLocal {} state-0.6.0/src/thread_local/thread_id.rs000064400000000000000000000037461046102023000164620ustar 00000000000000// Copyright 2017 Amanieu d'Antras // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::collections::BinaryHeap; use std::sync::{Mutex, MutexGuard}; use std::usize; use crate::InitCell; // Thread ID manager which allocates thread IDs. It attempts to aggressively // reuse thread IDs where possible to avoid cases where a ThreadLocal grows // indefinitely when it is used by many short-lived threads. struct ThreadIdManager { limit: usize, free_list: BinaryHeap, } impl ThreadIdManager { fn new() -> ThreadIdManager { ThreadIdManager { limit: usize::MAX, free_list: BinaryHeap::new(), } } fn alloc(&mut self) -> usize { if let Some(id) = self.free_list.pop() { id } else { let id = self.limit; self.limit = self.limit.checked_sub(1).expect("Ran out of thread IDs"); id } } fn free(&mut self, id: usize) { self.free_list.push(id); } } fn thread_id_manager() -> MutexGuard<'static, ThreadIdManager> { static THREAD_ID_MANAGER: InitCell> = InitCell::new(); THREAD_ID_MANAGER.get_or_init(|| Mutex::new(ThreadIdManager::new())).lock().unwrap() } // Non-zero integer which is unique to the current thread while it is running. // A thread ID may be reused after a thread exits. struct ThreadId(usize); impl ThreadId { fn new() -> ThreadId { ThreadId(thread_id_manager().alloc()) } } impl Drop for ThreadId { fn drop(&mut self) { thread_id_manager().free(self.0) } } thread_local!(static THREAD_ID: ThreadId = ThreadId::new()); /// Returns a non-zero ID for the current thread pub fn get() -> Option { THREAD_ID.try_with(|x| x.0).ok() } state-0.6.0/src/thread_local/unreachable.rs000064400000000000000000000044261046102023000170040ustar 00000000000000// Copyright 2017 Amanieu d'Antras // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! # unreachable //! inlined from https://github.com/reem/rust-unreachable/ //! //! An unreachable code optimization hint in stable rust, and some useful //! extension traits for `Option` and `Result`. //! /// Hint to the optimizer that any code path which calls this function is /// statically unreachable and can be removed. /// /// Calling this function in reachable code invokes undefined behavior. Be /// very, very sure this is what you want; often, a simple `panic!` is more /// suitable. #[inline] pub unsafe fn unreachable() -> ! { /// The empty type for cases which can't occur. enum Void { } let x: &Void = ::std::mem::transmute(1usize); match *x {} } /// An extension trait for `Option` providing unchecked unwrapping methods. pub trait UncheckedOptionExt { /// Get the value out of this Option without checking for None. unsafe fn unchecked_unwrap(self) -> T; /// Assert that this Option is a None to the optimizer. unsafe fn unchecked_unwrap_none(self); } /// An extension trait for `Result` providing unchecked unwrapping methods. pub trait UncheckedResultExt { /// Get the value out of this Result without checking for Err. unsafe fn unchecked_unwrap_ok(self) -> T; /// Get the error out of this Result without checking for Ok. unsafe fn unchecked_unwrap_err(self) -> E; } impl UncheckedOptionExt for Option { unsafe fn unchecked_unwrap(self) -> T { match self { Some(x) => x, None => unreachable() } } unsafe fn unchecked_unwrap_none(self) { if self.is_some() { unreachable() } } } impl UncheckedResultExt for Result { unsafe fn unchecked_unwrap_ok(self) -> T { match self { Ok(x) => x, Err(_) => unreachable() } } unsafe fn unchecked_unwrap_err(self) -> E { match self { Ok(_) => unreachable(), Err(e) => e } } }state-0.6.0/src/tls.rs000064400000000000000000000156441046102023000127200ustar 00000000000000use std::fmt; use crate::thread_local::ThreadLocal; use crate::cell::InitCell; pub struct LocalValue { tls: ThreadLocal, init_fn: Box T + Send + Sync>, } impl LocalValue { pub fn new T>(init_fn: F) -> LocalValue where F: Send + Sync + 'static { LocalValue { tls: ThreadLocal::new(), init_fn: Box::new(init_fn), } } pub fn get(&self) -> &T { self.tls.get_or(|| (self.init_fn)()) } pub fn try_get(&self) -> Option<&T> { self.tls.get_or_try(|| Ok::((self.init_fn)())) .and_then(|res| res.ok()) } } /// A thread-local init-once-per-thread cell for thread-local values. /// /// A `LocalInitCell` instance allows global access to a `n` per-thread values, /// all of which are initialized in the same manner when the value is first /// retrieved from a thread. // /// The initialization function for values in `LocalInitCell` is specified via /// the [set](#method.set) method. The initialization function must be set /// before a value is attempted to be retrieved via the [get](#method.get) /// method. The [try_get](#method.try_get) can be used to determine whether the /// `LocalInitCell` has been initialized before attempting to retrieve a value. /// /// For safety reasons, values stored in `LocalInitCell` must be `Send + /// 'static`. /// /// # Comparison with `InitCell` /// /// When the use-case allows, there are two primary advantages to using a /// `LocalInitCell` instance over a `InitCell` instance: /// /// * Values stored in `LocalInitCell` do not need to implement `Sync`. /// * There is no synchronization overhead when setting a value. /// /// The primary disadvantages are: /// /// * Values are recomputed once per thread on `get()` where `InitCell` never /// recomputes values. /// * Values need to be `'static` where `InitCell` imposes no such restriction. /// /// Values `LocalInitCell` are _not_ the same across different threads. Any /// modifications made to the stored value in one thread are _not_ visible in /// another. Furthermore, because Rust reuses thread IDs, a new thread is _not_ /// guaranteed to receive a newly initialized value on its first call to `get`. /// /// # Usage /// /// **This type is only available when the `"tls"` feature is enabled.** To /// enable the feature, include the `state` dependency in your `Cargo.toml` as /// follows: /// /// ```toml /// [dependencies] /// state = { version = "0.6.0", features = ["tls"] } /// ``` /// /// # Example /// /// The following example uses `LocalInitCell` to store a per-thread count: /// /// ```rust /// # extern crate state; /// # use std::cell::Cell; /// # use std::thread; /// # use state::LocalInitCell; /// static COUNT: LocalInitCell> = LocalInitCell::new(); /// /// fn check_count() { /// let count = COUNT.get(); /// /// // initialize the state, in case we reuse thread IDs /// count.set(0); /// /// // increment it, non-atomically /// count.set(count.get() + 1); /// /// // The count should always be 1 since the state is thread-local. /// assert_eq!(count.get(), 1); /// } /// /// fn main() { /// // setup the initializer for thread-local state /// COUNT.set(|| Cell::new(0)); /// /// // spin up many threads that call `check_count`. /// let mut threads = vec![]; /// for i in 0..10 { /// threads.push(thread::spawn(|| check_count())); /// } /// /// // Wait for all of the thread to finish. /// for thread in threads { /// thread.join().expect("correct count"); /// } /// } /// ``` pub struct LocalInitCell { cell: InitCell> } impl LocalInitCell { /// Create a new, uninitialized cell. /// /// # Example /// /// ```rust /// use state::LocalInitCell; /// /// static MY_GLOBAL: LocalInitCell = LocalInitCell::new(); /// ``` pub const fn new() -> LocalInitCell { LocalInitCell { cell: InitCell::new() } } } impl LocalInitCell { /// Sets the initialization function for this local cell to /// `state_init` if it has not already been set before. The function will be /// used to initialize values on the first access from a thread with a new /// thread ID. /// /// If a value has previously been set, `self` is unchanged and `false` is /// returned. Otherwise `true` is returned. /// /// # Example /// /// ```rust /// # use state::LocalInitCell; /// static MY_GLOBAL: LocalInitCell<&'static str> = LocalInitCell::new(); /// /// assert_eq!(MY_GLOBAL.set(|| "Hello, world!"), true); /// assert_eq!(MY_GLOBAL.set(|| "Goodbye, world!"), false); /// ``` #[inline] pub fn set T>(&self, state_init: F) -> bool where F: Send + Sync + 'static { self.cell.set(LocalValue::new(state_init)) } /// Attempts to borrow the value in this cell. If this is the /// first time a thread with the current thread ID has called `get` or /// `try_get` for `self`, the value will be initialized using the /// initialization function. /// /// Returns `Some` if the state has previously been [set](#method.set). /// Otherwise returns `None`. /// /// # Example /// /// ```rust /// # use state::LocalInitCell; /// static MY_GLOBAL: LocalInitCell<&'static str> = LocalInitCell::new(); /// /// assert_eq!(MY_GLOBAL.try_get(), None); /// /// MY_GLOBAL.set(|| "Hello, world!"); /// /// assert_eq!(MY_GLOBAL.try_get(), Some(&"Hello, world!")); /// ``` #[inline] pub fn try_get(&self) -> Option<&T> { self.cell.try_get().and_then(|v| v.try_get()) } /// If this is the first time a thread with the current thread ID has called /// `get` or `try_get` for `self`, the value will be initialized using the /// initialization function. /// /// # Panics /// /// Panics if an initialization function has not previously been /// [set](#method.set). Use [try_get](#method.try_get) for a non-panicking /// version. /// /// # Example /// /// ```rust /// # use state::LocalInitCell; /// static MY_GLOBAL: LocalInitCell<&'static str> = LocalInitCell::new(); /// /// MY_GLOBAL.set(|| "Hello, world!"); /// assert_eq!(*MY_GLOBAL.get(), "Hello, world!"); /// ``` #[inline] pub fn get(&self) -> &T { self.try_get().expect("localcell::get(): called get() before set()") } } #[cfg(test)] static_assertions::assert_impl_all!(LocalValue: Send, Sync); #[cfg(test)] static_assertions::assert_impl_all!(LocalInitCell: Send, Sync); impl fmt::Debug for LocalInitCell { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self.try_get() { Some(object) => object.fmt(f), None => write!(f, "[uninitialized local cell]") } } } state-0.6.0/src/type_map.rs000064400000000000000000000532701046102023000137310ustar 00000000000000use std::marker::PhantomData; use std::collections::HashMap; use std::hash::BuildHasherDefault; use std::any::{Any, TypeId}; use crate::init::Init; use crate::ident_hash::IdentHash; use crate::shim::cell::UnsafeCell; use crate::shim::sync::atomic::{AtomicUsize, Ordering}; use crate::shim::thread::yield_now; #[cfg(feature = "tls")] use crate::tls::LocalValue; /// A type map storing values based on types. /// /// A type map stores at most _one_ instance of given type as well as _n_ /// thread-local instances of a given type. /// /// ## Type Bounds /// /// A `TypeMap` can store values that are both `Send + Sync`, just `Send`, or /// neither. The [`TypeMap!`](macro.TypeMap.html) macro is used to specify the /// kind of type map: /// /// ```rust /// use state::TypeMap; /// /// // Values must implement `Send + Sync`. The type_map itself is `Send + Sync`. /// let type_map: TypeMap![Send + Sync] = ::new(); /// let type_map: TypeMap![Sync + Send] = ::new(); /// /// // Values must implement `Send`. The type_map itself is `Send`, `!Sync`. /// let type_map: TypeMap![Send] = ::new(); /// /// // Values needn't implement `Send` nor `Sync`. `TypeMap` is `!Send`, `!Sync`. /// let type_map: TypeMap![] = ::new(); /// ``` /// /// ## Setting State /// /// Global state is set via the [`set()`](TypeMap::set()) method and retrieved /// via the [`get()`](TypeMap::get()) method. The type of the value being set /// must meet the bounds of the `TypeMap`. /// /// ```rust /// use state::TypeMap; /// /// fn f_send_sync(value: T) { /// let type_map = ::new(); /// type_map.set(value.clone()); /// /// let type_map = ::new(); /// type_map.set(value.clone()); /// /// let type_map = ::new(); /// type_map.set(value.clone()); /// } /// /// fn f_send(value: T) { /// // This would fail to compile since `T` may not be `Sync`. /// // let type_map = ::new(); /// // type_map.set(value.clone()); /// /// let type_map = ::new(); /// type_map.set(value.clone()); /// /// let type_map = ::new(); /// type_map.set(value.clone()); /// } /// /// fn f(value: T) { /// // This would fail to compile since `T` may not be `Sync` or `Send`. /// // let type_map = ::new(); /// // type_map.set(value.clone()); /// /// // This would fail to compile since `T` may not be `Send`. /// // let type_map = ::new(); /// // type_map.set(value.clone()); /// /// let type_map = ::new(); /// type_map.set(value); /// } /// /// // If `TypeMap` is `Send + Sync`, it can be `const`-constructed. /// static TYPE_MAP: TypeMap![Send + Sync] = ::new(); /// /// TYPE_MAP.set(String::new()); /// TYPE_MAP.get::(); /// ``` /// /// ## Freezing /// /// By default, all `get`, `set`, `get_local`, and `set_local` calls result in /// synchronization overhead for safety. However, if calling `set` or /// `set_local` is no longer required, the overhead can be eliminated by /// _freezing_ the `TypeMap`. A frozen type map can only be read and never /// written to. Attempts to write to a frozen type map will be ignored. /// /// To freeze a `TypeMap`, call [`freeze()`](TypeMap::freeze()). A frozen map /// can never be thawed. To check if a type map is frozen, call /// [`is_frozen()`](TypeMap::is_frozen()). /// /// ## Thread-Local State /// /// Thread-local state on a `Send + Sync` type map is set via the /// [`set_local()`](TypeMap::set_local()) method and retrieved via the /// [`get_local()`](TypeMap::get_local()) method. The type of the value being /// set must be transferable across thread boundaries but need not be /// thread-safe. In other words, it must satisfy `Send + 'static` but not /// necessarily `Sync`. Values retrieved from thread-local state are exactly /// that: local to the current thread. As such, you cannot use thread-local /// state to synchronize across multiple threads. /// /// Thread-local state is initialized on an as-needed basis. The function used /// to initialize the thread-local state is passed in as an argument to /// `set_local`. When the state is retrieved from a given thread for the first /// time, the function is executed to generate the initial value. The function /// is executed at most once per thread. The same function is used for /// initialization across all threads. /// /// **Note:** Rust reuses thread IDs across multiple threads. This means that is /// possible to set thread-local state in thread A, have that thread die, start /// a new thread B, and access the state set in tread A in thread B. /// /// ### Example /// /// Set and later retrieve a value of type T: /// /// ```rust /// # struct T; /// # impl T { fn new() -> T { T } } /// # #[cfg(not(feature = "tls"))] fn test() { } /// # #[cfg(feature = "tls")] fn test() { /// use state::TypeMap; /// /// static TYPE_MAP: TypeMap![Send + Sync] = ::new(); /// /// TYPE_MAP.set_local(|| T::new()); /// TYPE_MAP.get_local::(); /// # } /// # fn main() { test() } /// ``` pub struct TypeMap { init: Init, map: UnsafeCell>, mutex: AtomicUsize, frozen: bool, /// Force !Send (and carry the type). _kind: PhantomData<*mut K> } mod kind { pub trait Kind { } pub struct Send; impl Kind for Send {} pub struct SendSync; impl Kind for SendSync {} pub struct Neither; impl Kind for Neither {} } pub type TypeMapSend = TypeMap; pub type TypeMapSendSync = TypeMap; pub type TypeMapNeither = TypeMap; /// Type constructor for [`TypeMap`](struct@TypeMap) variants. #[macro_export] macro_rules! TypeMap { () => ($crate::type_map::TypeMapNeither); (Send) => ($crate::type_map::TypeMapSend); (Send + Sync) => ($crate::type_map::TypeMapSendSync); (Sync + Send) => ($crate::type_map::TypeMapSendSync); } macro_rules! new { () => ( TypeMap { init: Init::new(), map: UnsafeCell::new(None), mutex: AtomicUsize::new(0), frozen: false, _kind: PhantomData, } ) } type TypeIdMap = HashMap, BuildHasherDefault>; impl TypeMap { /// Creates a new type map with no stored values. /// /// ## Example /// /// Create a globally available type map: /// /// ```rust /// use state::TypeMap; /// /// static TYPE_MAP: TypeMap![Send + Sync] = ::new(); /// ``` #[cfg(not(loom))] pub const fn new() -> Self { new!() } #[cfg(loom)] pub fn new() -> Self { new!() } /// Sets the global state for type `T` if it has not been set before and /// `self` is not frozen. /// /// If the state for `T` has previously been set or `self` is frozen, the /// state is unchanged and `false` is returned. Otherwise `true` is /// returned. /// /// # Example /// /// Set the state for `AtomicUsize`. The first `set` is succesful while the /// second fails. /// /// ```rust /// # use std::sync::atomic::AtomicUsize; /// use state::TypeMap; /// /// static TYPE_MAP: TypeMap![Send + Sync] = ::new(); /// /// assert_eq!(TYPE_MAP.set(AtomicUsize::new(0)), true); /// assert_eq!(TYPE_MAP.set(AtomicUsize::new(1)), false); /// ``` #[inline] pub fn set(&self, state: T) -> bool { unsafe { self._set(state) } } /// Sets the thread-local state for type `T` if it has not been set before. /// /// The state for type `T` will be initialized via the `state_init` function as /// needed. If the state for `T` has previously been set, the state is unchanged /// and `false` is returned. Returns `true` if the thread-local state is /// successfully set to be initialized with `state_init`. /// /// # Example /// /// ```rust /// # use std::cell::Cell; /// use state::TypeMap; /// /// static TYPE_MAP: TypeMap![Send + Sync] = ::new(); /// /// struct MyState(Cell); /// /// assert_eq!(TYPE_MAP.set_local(|| MyState(Cell::new(1))), true); /// assert_eq!(TYPE_MAP.set_local(|| MyState(Cell::new(2))), false); /// ``` #[inline] #[cfg(feature = "tls")] pub fn set_local(&self, state_init: F) -> bool where T: Send + 'static, F: Fn() -> T + Send + Sync + 'static { self.set::>(LocalValue::new(state_init)) } /// Attempts to retrieve the thread-local state for type `T`. /// /// Returns `Some` if the state has previously been set via /// [set_local](#method.set_local). Otherwise returns `None`. /// /// # Example /// /// ```rust /// # use std::cell::Cell; /// use state::TypeMap; /// /// static TYPE_MAP: TypeMap![Send + Sync] = ::new(); /// /// struct MyState(Cell); /// /// TYPE_MAP.set_local(|| MyState(Cell::new(10))); /// /// let my_state = TYPE_MAP.try_get_local::().expect("MyState"); /// assert_eq!(my_state.0.get(), 10); /// ``` #[inline] #[cfg(feature = "tls")] pub fn try_get_local(&self) -> Option<&T> { // TODO: This will take a lock on the HashMap unnecessarily. Ideally // we'd have a `HashMap` per thread mapping from TypeId to (T, F). self.try_get::>().map(|value| value.get()) } /// Retrieves the thread-local state for type `T`. /// /// # Panics /// /// Panics if the thread-local state for type `T` has not previously been set /// via [set_local](#method.set_local). Use /// [try_get_local](#method.try_get_local) for a non-panicking version. /// /// # Example /// /// ```rust /// # use std::cell::Cell; /// use state::TypeMap; /// /// static TYPE_MAP: TypeMap![Send + Sync] = ::new(); /// /// struct MyState(Cell); /// /// TYPE_MAP.set_local(|| MyState(Cell::new(10))); /// /// let my_state = TYPE_MAP.get_local::(); /// assert_eq!(my_state.0.get(), 10); /// ``` #[inline] #[cfg(feature = "tls")] pub fn get_local(&self) -> &T { self.try_get_local::() .expect("type_map::get_local(): get_local() called before set_local()") } } unsafe impl Send for TypeMap { } unsafe impl Sync for TypeMap { } #[cfg(test)] static_assertions::assert_impl_all!(TypeMap![Send + Sync]: Send, Sync); #[cfg(test)] static_assertions::assert_impl_all!(TypeMap![Sync + Send]: Send, Sync); impl TypeMap { /// Creates a new type map with no stored values. /// /// # Example /// /// ```rust /// use std::cell::Cell; /// /// use state::TypeMap; /// /// let type_map = ::new(); /// /// let value: Cell = Cell::new(10); /// type_map.set(value); /// assert_eq!(type_map.get::>().get(), 10); /// /// type_map.get::>().set(99); /// assert_eq!(type_map.get::>().get(), 99); /// ``` pub fn new() -> Self { // SAFETY: this can't be `const` or we violate `Sync`. new!() } /// Sets the global state for type `T` if it has not been set before and /// `self` is not frozen. /// /// If the state for `T` has previously been set or `self` is frozen, the /// state is unchanged and `false` is returned. Otherwise `true` is /// returned. /// /// # Example /// /// Set the state. The first `set` is succesful while the second fails. /// /// ```rust /// # use std::sync::atomic::AtomicUsize; /// use state::TypeMap; /// /// let type_map = ::new(); /// assert!(type_map.set(AtomicUsize::new(0))); /// assert!(!type_map.set(AtomicUsize::new(1))); /// ``` #[inline] pub fn set(&self, state: T) -> bool { unsafe { self._set(state) } } } unsafe impl Send for TypeMap { } #[cfg(test)] static_assertions::assert_impl_all!(TypeMap![Send]: Send); #[cfg(test)] static_assertions::assert_not_impl_any!(TypeMap![Send]: Sync); #[cfg(test)] static_assertions::assert_not_impl_any!(TypeMap: Sync); impl TypeMap { /// Creates a new type_map with no stored values. /// /// # Example /// /// ```rust /// use std::cell::Cell; /// use state::TypeMap; /// /// let type_map = ::new(); /// /// let value: Cell = Cell::new(10); /// type_map.set(value); /// assert_eq!(type_map.get::>().get(), 10); /// /// type_map.get::>().set(99); /// assert_eq!(type_map.get::>().get(), 99); /// ``` pub fn new() -> Self { // SAFETY: this can't be `const` or we violate `Sync`. new!() } /// Sets the global state for type `T` if it has not been set before and /// `self` is not frozen. /// /// If the state for `T` has previously been set or `self` is frozen, the /// state is unchanged and `false` is returned. Otherwise `true` is /// returned. /// /// # Example /// /// Set the state. The first `set` is succesful while the second fails. /// /// ```rust /// use std::cell::Cell; /// use state::TypeMap; /// /// let type_map = ::new(); /// assert!(type_map.set(Cell::new(10))); /// assert!(!type_map.set(Cell::new(17))); /// ``` #[inline] pub fn set(&self, state: T) -> bool { unsafe { self._set(state) } } } #[cfg(test)] static_assertions::assert_not_impl_any!(TypeMap![]: Send, Sync); #[cfg(test)] static_assertions::assert_not_impl_any!(TypeMap: Send, Sync); impl TypeMap { // Initializes the `map` if needed. unsafe fn init_map_if_needed(&self) { if self.init.needed() { self.map.with_mut(|ptr| *ptr = Some(HashMap::<_, _, _>::default())); self.init.mark_complete(); } } // Initializes the `map` if needed and returns a mutable ref to it. // // SAFETY: Caller must ensure mutual exclusion of calls to this function // and/or calls to `map_ref`. #[inline(always)] #[allow(clippy::mut_from_ref)] unsafe fn map_mut(&self) -> &mut TypeIdMap { self.init_map_if_needed(); self.map.with_mut(|ptr| (*ptr).as_mut().unwrap()) } // Initializes the `map` if needed and returns an immutable ref to it. // // SAFETY: Caller must ensure mutual exclusion of calls to this function // and/or calls to `map_mut`. #[inline(always)] unsafe fn map_ref(&self) -> &TypeIdMap { self.init_map_if_needed(); self.map.with(|ptr| (*ptr).as_ref().unwrap()) } /// SAFETY: The caller needs to ensure that `T` has the required bounds /// `Sync` or `Send` bounds. unsafe fn _set(&self, state: T) -> bool { if self.is_frozen() { return false; } self.lock(); let map = self.map_mut(); let type_id = TypeId::of::(); let already_set = map.contains_key(&type_id); if !already_set { map.insert(type_id, Box::new(state) as Box); } self.unlock(); !already_set } /// SAFETY: The caller needs to ensure that the `T` returned from the `f` is /// not dependent on the stability of memory slots in the map. It also needs /// to ensure that `f` does not panic if liveness is desired. unsafe fn with_map_ref<'a, F, T: 'a>(&'a self, f: F) -> T where F: FnOnce(&'a TypeIdMap) -> T { // If we're frozen, there can't be any concurrent writers, so we're // free to read this safely without taking a lock. if self.is_frozen() { f(self.map_ref()) } else { self.lock(); let result = f(self.map_ref()); self.unlock(); result } } /// Attempts to retrieve the global state for type `T`. /// /// Returns `Some` if the state has previously been [set](#method.set). /// Otherwise returns `None`. /// /// # Example /// /// ```rust /// # use std::sync::atomic::{AtomicUsize, Ordering}; /// use state::TypeMap; /// /// static TYPE_MAP: TypeMap![Send + Sync] = ::new(); /// /// struct MyState(AtomicUsize); /// /// // State for `T` is initially unset. /// assert!(TYPE_MAP.try_get::().is_none()); /// /// TYPE_MAP.set(MyState(AtomicUsize::new(0))); /// /// let my_state = TYPE_MAP.try_get::().expect("MyState"); /// assert_eq!(my_state.0.load(Ordering::Relaxed), 0); /// ``` #[inline] pub fn try_get(&self) -> Option<&T> { // SAFETY: `deanonymization` takes a potentially unstable refrence to an // `AnyObject` and converts it into a stable address: it is converting // an `&Box` into the inner `&T`. The inner item is never // dropped until `self` is dropped: it is never replaced. unsafe { self.with_map_ref(|map| { map.get(&TypeId::of::()).and_then(|ptr| ptr.downcast_ref()) }) } } /// Retrieves the global state for type `T`. /// /// # Panics /// /// Panics if the state for type `T` has not previously been /// [`set()`](Self::set()). Use [`try_get()`](Self::try_get()) for a /// non-panicking version. /// /// # Example /// /// ```rust /// # use std::sync::atomic::{AtomicUsize, Ordering}; /// use state::TypeMap; /// /// static TYPE_MAP: TypeMap![Send + Sync] = ::new(); /// /// struct MyState(AtomicUsize); /// /// TYPE_MAP.set(MyState(AtomicUsize::new(0))); /// /// let my_state = TYPE_MAP.get::(); /// assert_eq!(my_state.0.load(Ordering::Relaxed), 0); /// ``` #[inline] pub fn get(&self) -> &T { self.try_get() .expect("type_map::get(): get() called before set() for given type") } /// Freezes the type_map. A frozen type_map disallows writes allowing for /// synchronization-free reads. /// /// # Example /// /// ```rust /// use state::TypeMap; /// /// // A new type_map starts unfrozen and can be written to. /// let mut type_map = ::new(); /// assert_eq!(type_map.set(1usize), true); /// /// // While unfrozen, `get`s require synchronization. /// assert_eq!(type_map.get::(), &1); /// /// // After freezing, calls to `set` or `set_local `will fail. /// type_map.freeze(); /// assert_eq!(type_map.set(1u8), false); /// assert_eq!(type_map.set("hello"), false); /// /// // Calls to `get` or `get_local` are synchronization-free when frozen. /// assert_eq!(type_map.try_get::(), None); /// assert_eq!(type_map.get::(), &1); /// ``` #[inline(always)] pub fn freeze(&mut self) { self.frozen = true; } /// Returns `true` if the type_map is frozen and `false` otherwise. /// /// # Example /// /// ```rust /// use state::TypeMap; /// /// // A new type_map starts unfrozen and is frozen using `freeze`. /// let mut type_map = ::new(); /// assert_eq!(type_map.is_frozen(), false); /// /// type_map.freeze(); /// assert_eq!(type_map.is_frozen(), true); /// ``` #[inline(always)] pub fn is_frozen(&self) -> bool { self.frozen } /// Returns the number of distinctly typed values in `self`. /// /// # Example /// /// ```rust /// use state::TypeMap; /// /// let type_map = ::new(); /// assert_eq!(type_map.len(), 0); /// /// assert_eq!(type_map.set(1usize), true); /// assert_eq!(type_map.len(), 1); /// /// assert_eq!(type_map.set(2usize), false); /// assert_eq!(type_map.len(), 1); /// /// assert_eq!(type_map.set(1u8), true); /// assert_eq!(type_map.len(), 2); /// ``` #[inline] pub fn len(&self) -> usize { // SAFETY: We retrieve a `usize`, which is clearly stable. unsafe { self.with_map_ref(|map| map.len()) } } /// Returns `true` if `self` contains zero values. /// /// # Example /// /// ```rust /// use state::TypeMap; /// /// let type_map = ::new(); /// assert!(type_map.is_empty()); /// /// assert_eq!(type_map.set(1usize), true); /// assert!(!type_map.is_empty()); /// ``` #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } #[inline(always)] fn lock(&self) { while self.mutex.compare_exchange(0, 1, Ordering::AcqRel, Ordering::Relaxed).is_err() { yield_now(); } } #[inline(always)] fn unlock(&self) { assert!(self.mutex.compare_exchange(1, 0, Ordering::AcqRel, Ordering::Relaxed).is_ok()); } } impl Default for TypeMap![Send + Sync] { fn default() -> Self { ::new() } } impl Default for TypeMap![Send] { fn default() -> Self { ::new() } } impl Default for TypeMap![] { fn default() -> Self { ::new() } } impl std::fmt::Debug for TypeMap { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("TypeMap") .field("len", &self.len()) .finish() } } state-0.6.0/tests/loom.rs000064400000000000000000000076321046102023000134350ustar 00000000000000#![cfg(loom)] use loom::sync::Arc; use loom::sync::atomic::{AtomicUsize, Ordering::Relaxed}; use loom::thread; #[test] fn init_exclusive() { use state::private::init::Init; const THREADS: usize = 2; loom::model(|| { let init = Arc::new(Init::new()); let value = Arc::new(AtomicUsize::new(0)); let mut threads = vec![]; for _ in 1..(THREADS + 1) { let init = init.clone(); let value = value.clone(); let thread = thread::spawn(move || { if init.needed() { value.fetch_add(1, Relaxed); init.mark_complete(); } }); threads.push(thread); } for thread in threads { thread.join().unwrap(); } assert_eq!(value.load(Relaxed), 1); }); } #[test] fn init_completed() { use state::private::init::Init; const THREADS: usize = 2; loom::model(|| { let init = Arc::new(Init::new()); let value = Arc::new(AtomicUsize::new(0)); let mut threads = vec![]; for _ in 1..(THREADS + 1) { let init = init.clone(); let value = value.clone(); let thread = thread::spawn(move || { if init.has_completed() { assert_eq!(value.load(Relaxed), 1); } if init.needed() { value.fetch_add(1, Relaxed); init.mark_complete(); } }); threads.push(thread); } for thread in threads { thread.join().unwrap(); } assert_eq!(value.load(Relaxed), 1); }); } #[test] fn cell() { use state::InitCell; const THREADS: usize = 2; loom::model(|| { let cell = Arc::new(InitCell::::new()); let mut threads = vec![]; for _ in 1..(THREADS + 1) { let cell = cell.clone(); let thread = thread::spawn(move || { cell.set(10); assert_eq!(cell.try_get(), Some(&10)); assert!(!cell.set(20)); assert_eq!(cell.try_get(), Some(&10)); }); threads.push(thread); } for thread in threads { thread.join().unwrap(); } }); } #[test] fn type_map1() { use state::TypeMap; const THREADS: usize = 2; loom::model(|| { let type_map = Arc::new(::new()); let mut threads = vec![]; for _ in 1..(THREADS + 1) { let type_map = type_map.clone(); let thread = thread::spawn(move || { assert_eq!(type_map.try_get::(), None); type_map.set::(10); assert_eq!(type_map.try_get::(), Some(&10)); assert!(!type_map.set::(11)); assert_eq!(type_map.try_get::(), Some(&10)); }); threads.push(thread); } for thread in threads { thread.join().unwrap(); } assert_eq!(type_map.len(), 1); }); } #[test] fn type_map2() { use state::TypeMap; const THREADS: usize = 2; loom::model(|| { let type_map = Arc::new(::new()); let mut threads = vec![]; for _ in 1..(THREADS + 1) { let type_map = type_map.clone(); let thread = thread::spawn(move || { if type_map.try_get::>().is_some() { assert!(!type_map.set::>(Box::new(10))); } type_map.set::>(Box::new(10)); assert_eq!(**type_map.try_get::>().unwrap(), 10); }); threads.push(thread); } for thread in threads { thread.join().unwrap(); } assert_eq!(type_map.len(), 1); }); } state-0.6.0/tests/main.rs000064400000000000000000000252761046102023000134170ustar 00000000000000extern crate state; use std::sync::{Arc, RwLock}; // Tiny structures to test that dropping works as expected. struct DroppingStruct(Arc>); struct DroppingStructWrap(DroppingStruct); impl Drop for DroppingStruct { fn drop(&mut self) { *self.0.write().unwrap() = true; } } // Ensure our DroppingStruct works as intended. #[test] fn test_dropping_struct() { let drop_flag = Arc::new(RwLock::new(false)); let dropping_struct = DroppingStruct(drop_flag.clone()); drop(dropping_struct); assert_eq!(*drop_flag.read().unwrap(), true); let drop_flag = Arc::new(RwLock::new(false)); let dropping_struct = DroppingStruct(drop_flag.clone()); let wrapper = DroppingStructWrap(dropping_struct); drop(wrapper); assert_eq!(*drop_flag.read().unwrap(), true); } mod type_map_tests { use super::{DroppingStruct, DroppingStructWrap}; use super::state::TypeMap; use std::sync::{Arc, RwLock}; use std::thread; // We use one `TYPE_MAP` to get an implicit test since each `test` runs in // a different thread. This means we have to `set` different types in each // test if we want the `set` to succeed. static TYPE_MAP: TypeMap![Send + Sync] = ::new(); #[test] fn simple_set_get() { assert!(TYPE_MAP.set(1u32)); assert_eq!(*TYPE_MAP.get::(), 1); } #[test] fn dst_set_get() { assert!(TYPE_MAP.set::<[u32; 4]>([1, 2, 3, 4u32])); assert_eq!(*TYPE_MAP.get::<[u32; 4]>(), [1, 2, 3, 4]); } #[test] fn set_get_remote() { thread::spawn(|| { TYPE_MAP.set(10isize); }).join().unwrap(); assert_eq!(*TYPE_MAP.get::(), 10); } #[test] fn two_put_get() { assert!(TYPE_MAP.set("Hello, world!".to_string())); let s_old = TYPE_MAP.get::(); assert_eq!(s_old, "Hello, world!"); assert!(!TYPE_MAP.set::("Bye bye!".into())); assert_eq!(TYPE_MAP.get::(), "Hello, world!"); assert_eq!(TYPE_MAP.get::(), s_old); } #[test] fn many_puts_only_one_succeeds() { let mut threads = vec![]; for _ in 0..1000 { threads.push(thread::spawn(|| { TYPE_MAP.set(10i64) })) } let results: Vec = threads.into_iter().map(|t| t.join().unwrap()).collect(); assert_eq!(results.into_iter().filter(|&b| b).count(), 1); assert_eq!(*TYPE_MAP.get::(), 10); } // Ensure setting when already set doesn't cause a drop. #[test] fn test_no_drop_on_set() { let drop_flag = Arc::new(RwLock::new(false)); let dropping_struct = DroppingStruct(drop_flag.clone()); let _drop_flag_ignore = Arc::new(RwLock::new(false)); let _dropping_struct_ignore = DroppingStruct(_drop_flag_ignore.clone()); TYPE_MAP.set::(dropping_struct); assert!(!TYPE_MAP.set::(_dropping_struct_ignore)); assert_eq!(*drop_flag.read().unwrap(), false); } // Ensure dropping a type_map drops its contents. #[test] fn drop_inners_on_drop() { let drop_flag_a = Arc::new(RwLock::new(false)); let dropping_struct_a = DroppingStruct(drop_flag_a.clone()); let drop_flag_b = Arc::new(RwLock::new(false)); let dropping_struct_b = DroppingStructWrap(DroppingStruct(drop_flag_b.clone())); { let type_map = ::new(); type_map.set(dropping_struct_a); assert_eq!(*drop_flag_a.read().unwrap(), false); type_map.set(dropping_struct_b); assert_eq!(*drop_flag_a.read().unwrap(), false); assert_eq!(*drop_flag_b.read().unwrap(), false); } assert_eq!(*drop_flag_a.read().unwrap(), true); assert_eq!(*drop_flag_b.read().unwrap(), true); } } #[cfg(feature = "tls")] mod type_map_tests_tls { use std::sync::{Arc, Barrier}; use super::state::TypeMap; use std::cell::Cell; use std::thread; // We use one `TYPE_MAP` to get an implicit test since each `test` runs in // a different thread. This means we have to `set` different types in each // test if we want the `set` to succeed. static TYPE_MAP: TypeMap![Send + Sync] = ::new(); #[test] fn test_simple() { assert!(TYPE_MAP.try_get_local::().is_none()); assert!(TYPE_MAP.set_local(|| 1u32)); assert_eq!(*TYPE_MAP.get_local::(), 1); } #[test] fn test_double_put() { assert!(TYPE_MAP.set_local(|| 1i32)); assert!(!TYPE_MAP.set_local(|| 1i32)); } #[test] fn not_unique_when_sent() { assert!(TYPE_MAP.set_local(|| 1i64)); let value = TYPE_MAP.get_local::(); thread::spawn(move || { assert_eq!(*value, 1i64); }).join().expect("Panic."); } #[test] fn type_map_tls_really_is_tls() { const THREADS: usize = 50; let barriers = Arc::new((Barrier::new(THREADS), Barrier::new(THREADS))); assert!(TYPE_MAP.set_local(|| Cell::new(0u8))); let mut threads = vec![]; for i in 1..THREADS { let barriers = barriers.clone(); threads.push(thread::spawn(move || { barriers.0.wait(); assert_eq!(TYPE_MAP.get_local::>().get(), 0); TYPE_MAP.get_local::>().set(i as u8); let v = TYPE_MAP.get_local::>().get(); barriers.1.wait(); v })); } barriers.0.wait(); TYPE_MAP.get_local::>().set(0); barriers.1.wait(); let vals = threads.into_iter().map(|t| t.join().unwrap()).collect::>(); for (i, val) in vals.into_iter().enumerate() { assert_eq!((i + 1) as u8, val); } assert_eq!(TYPE_MAP.get_local::>().get(), 0); } #[test] fn type_map_tls_really_is_tls_take_2() { thread::spawn(|| { assert!(TYPE_MAP.set_local(|| Cell::new(1i8))); TYPE_MAP.get_local::>().set(2); thread::spawn(|| { assert_eq!(TYPE_MAP.get_local::>().get(), 1); }).join().expect("inner join"); }).join().expect("outer join"); } } mod cell_tests { use super::DroppingStruct; use super::state::InitCell; use std::sync::{Arc, RwLock}; use std::thread; #[test] fn simple_put_get() { static CELL: InitCell = InitCell::new(); assert!(CELL.set(10)); assert_eq!(*CELL.get(), 10); } #[test] fn no_double_put() { static CELL: InitCell = InitCell::new(); assert!(CELL.set(1)); assert!(!CELL.set(5)); assert_eq!(*CELL.get(), 1); } #[test] fn many_puts_only_one_succeeds() { static CELL: InitCell = InitCell::new(); let mut threads = vec![]; for _ in 0..1000 { threads.push(thread::spawn(|| { let was_set = CELL.set(10); assert_eq!(*CELL.get(), 10); was_set })) } let results: Vec = threads.into_iter().map(|t| t.join().unwrap()).collect(); assert_eq!(results.into_iter().filter(|&b| b).count(), 1); assert_eq!(*CELL.get(), 10); } #[test] fn dst_set_get() { static CELL: InitCell<[u32; 4]> = InitCell::new(); assert!(CELL.set([1, 2, 3, 4])); assert_eq!(*CELL.get(), [1, 2, 3, 4]); } // Ensure dropping a `InitCell` drops `T`. #[test] fn drop_inners_on_drop() { let drop_flag = Arc::new(RwLock::new(false)); let dropping_struct = DroppingStruct(drop_flag.clone()); { let cell = InitCell::new(); assert!(cell.set(dropping_struct)); assert_eq!(*drop_flag.read().unwrap(), false); } assert_eq!(*drop_flag.read().unwrap(), true); } #[test] fn clone() { let cell: InitCell = InitCell::new(); assert!(cell.try_get().is_none()); let cell_clone = cell.clone(); assert!(cell_clone.try_get().is_none()); assert!(cell.set(10)); let cell_clone = cell.clone(); assert_eq!(*cell_clone.get(), 10); } } #[cfg(feature = "tls")] mod cell_tests_tls { use super::state::LocalInitCell; use std::thread; use std::cell::Cell; use std::sync::{Arc, Barrier}; #[test] fn simple_put_get() { static CELL: LocalInitCell = LocalInitCell::new(); assert!(CELL.set(|| 10)); assert_eq!(*CELL.get(), 10); } #[test] fn no_double_put() { static CELL: LocalInitCell = LocalInitCell::new(); assert!(CELL.set(|| 1)); assert!(!CELL.set(|| 5)); assert_eq!(*CELL.get(), 1); } #[test] fn many_puts_only_one_succeeds() { static CELL: LocalInitCell = LocalInitCell::new(); let mut threads = vec![]; for _ in 0..1000 { threads.push(thread::spawn(|| { let was_set = CELL.set(|| 10); assert_eq!(*CELL.get(), 10); was_set })) } let results: Vec = threads.into_iter().map(|t| t.join().unwrap()).collect(); assert_eq!(results.into_iter().filter(|&b| b).count(), 1); assert_eq!(*CELL.get(), 10); } #[test] fn cell_tls_really_is_tls() { const THREADS: usize = 50; static CELL: LocalInitCell> = LocalInitCell::new(); let barriers = Arc::new((Barrier::new(THREADS), Barrier::new(THREADS))); assert!(CELL.set(|| Cell::new(0))); let mut threads = vec![]; for i in 1..50 { let barriers = barriers.clone(); threads.push(thread::spawn(move || { barriers.0.wait(); CELL.get().set(i); let val = CELL.get().get(); barriers.1.wait(); val })); } barriers.0.wait(); CELL.get().set(0); barriers.1.wait(); let vals = threads.into_iter().map(|t| t.join().unwrap()).collect::>(); for (i, val) in vals.into_iter().enumerate() { assert_eq!((i + 1) as u8, val); } assert_eq!(CELL.get().get(), 0); } #[test] fn cell_tls_really_is_tls_take_2() { static CELL: LocalInitCell> = LocalInitCell::new(); thread::spawn(|| { assert!(CELL.set(|| Cell::new(1))); CELL.get().set(2); thread::spawn(|| { assert_eq!(CELL.get().get(), 1); }).join().expect("inner join"); }).join().expect("outer join"); } }