├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples └── flags.rs ├── src └── lib.rs └── tests ├── compiletest.rs ├── test.rs └── ui ├── collect-unsized.rs ├── collect-unsized.stderr ├── non-sync.rs ├── non-sync.stderr ├── outer-attr.rs ├── outer-attr.stderr ├── submit-nonconst.rs ├── submit-nonconst.stderr ├── submit-unrecognized.rs ├── submit-unrecognized.stderr ├── used-linker.rs └── used-linker.stderr /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dtolnay 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: [cron: "40 1 * * *"] 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | pre_ci: 17 | uses: dtolnay/.github/.github/workflows/pre_ci.yml@master 18 | 19 | test: 20 | name: ${{matrix.name || format('Rust {0}', matrix.rust)}} 21 | needs: pre_ci 22 | if: needs.pre_ci.outputs.continue 23 | runs-on: ${{matrix.os}}-latest 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | rust: [nightly, beta, stable, 1.70.0] 28 | os: [ubuntu] 29 | include: 30 | - name: macOS 31 | os: macos 32 | rust: nightly 33 | - name: Windows (gnu) 34 | os: windows 35 | rust: nightly-x86_64-pc-windows-gnu 36 | - name: Windows (msvc) 37 | os: windows 38 | rust: nightly-x86_64-pc-windows-msvc 39 | timeout-minutes: 45 40 | steps: 41 | - uses: actions/checkout@v4 42 | - uses: dtolnay/rust-toolchain@master 43 | with: 44 | toolchain: ${{matrix.rust}} 45 | - name: Enable type layout randomization 46 | run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV 47 | if: startsWith(matrix.rust, 'nightly') 48 | shell: bash 49 | - name: Ignore Windows linker warning 50 | run: echo RUSTFLAGS=${RUSTFLAGS}\ -Alinker_messages >> $GITHUB_ENV 51 | if: matrix.rust == 'nightly-x86_64-pc-windows-gnu' 52 | shell: bash 53 | - run: cargo test 54 | - uses: actions/upload-artifact@v4 55 | if: matrix.os == 'ubuntu' && matrix.rust == 'nightly' && always() 56 | with: 57 | name: Cargo.lock 58 | path: Cargo.lock 59 | continue-on-error: true 60 | 61 | msrv: 62 | name: Rust 1.62.0 63 | needs: pre_ci 64 | if: needs.pre_ci.outputs.continue 65 | runs-on: ubuntu-latest 66 | timeout-minutes: 45 67 | steps: 68 | - uses: actions/checkout@v4 69 | - uses: dtolnay/rust-toolchain@1.62.0 70 | - run: cargo check 71 | 72 | doc: 73 | name: Documentation 74 | needs: pre_ci 75 | if: needs.pre_ci.outputs.continue 76 | runs-on: ubuntu-latest 77 | timeout-minutes: 45 78 | env: 79 | RUSTDOCFLAGS: -Dwarnings 80 | steps: 81 | - uses: actions/checkout@v4 82 | - uses: dtolnay/rust-toolchain@nightly 83 | - uses: dtolnay/install@cargo-docs-rs 84 | - run: cargo docs-rs 85 | 86 | clippy: 87 | name: Clippy 88 | runs-on: ubuntu-latest 89 | if: github.event_name != 'pull_request' 90 | timeout-minutes: 45 91 | steps: 92 | - uses: actions/checkout@v4 93 | - uses: dtolnay/rust-toolchain@clippy 94 | - run: cargo clippy -- -Dclippy::all -Dclippy::pedantic 95 | 96 | outdated: 97 | name: Outdated 98 | runs-on: ubuntu-latest 99 | if: github.event_name != 'pull_request' 100 | timeout-minutes: 45 101 | steps: 102 | - uses: actions/checkout@v4 103 | - uses: dtolnay/rust-toolchain@stable 104 | - uses: dtolnay/install@cargo-outdated 105 | - run: cargo outdated --exit-code 1 106 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "inventory" 3 | version = "0.3.20" 4 | authors = ["David Tolnay "] 5 | categories = ["development-tools::build-utils", "development-tools::procedural-macro-helpers", "no-std", "no-std::no-alloc"] 6 | description = "Typed distributed plugin registration" 7 | documentation = "https://docs.rs/inventory" 8 | edition = "2021" 9 | keywords = ["linkage"] 10 | license = "MIT OR Apache-2.0" 11 | repository = "https://github.com/dtolnay/inventory" 12 | rust-version = "1.62" 13 | 14 | [target.'cfg(target_family = "wasm")'.dependencies] 15 | rustversion = "1.0" 16 | 17 | [dev-dependencies] 18 | rustversion = "1.0" 19 | trybuild = { version = "1.0.89", features = ["diff"] } 20 | 21 | [package.metadata.docs.rs] 22 | targets = ["x86_64-unknown-linux-gnu"] 23 | rustdoc-args = [ 24 | "--generate-link-to-definition", 25 | "--extern-html-root-url=core=https://doc.rust-lang.org", 26 | "--extern-html-root-url=alloc=https://doc.rust-lang.org", 27 | "--extern-html-root-url=std=https://doc.rust-lang.org", 28 | ] 29 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Typed distributed plugin registration 2 | 3 | [github](https://github.com/dtolnay/inventory) 4 | [crates.io](https://crates.io/crates/inventory) 5 | [docs.rs](https://docs.rs/inventory) 6 | [build status](https://github.com/dtolnay/inventory/actions?query=branch%3Amaster) 7 | 8 | This crate provides a way to set up a plugin registry into which plugins can be 9 | registered from any source file linked into your application. There does not 10 | need to be a central list of all the plugins. 11 | 12 | ```toml 13 | [dependencies] 14 | inventory = "0.3" 15 | ``` 16 | 17 | *Supports rustc 1.62+* 18 | 19 |
20 | 21 | # Examples 22 | 23 | Suppose we are writing a command line flags library and want to allow any source 24 | file in the application to register command line flags that are relevant to it. 25 | 26 | This is the flag registration style used by [gflags] and is better suited for 27 | large scale development than maintaining a single central list of flags, as the 28 | central list would become an endless source of merge conflicts in an application 29 | developed simultaneously by thousands of developers. 30 | 31 | [gflags]: https://gflags.github.io/gflags/ 32 | 33 | ### Instantiating the plugin registry 34 | 35 | Let's use a `struct Flag` as the plugin type, which will contain the short name 36 | of the flag like `-v`, the full name like `--verbose`, and maybe other 37 | information like argument type and help text. We instantiate a plugin registry 38 | with an invocation of `inventory::collect!`. 39 | 40 | ```rust 41 | pub struct Flag { 42 | short: char, 43 | name: &'static str, 44 | /* ... */ 45 | } 46 | 47 | impl Flag { 48 | pub const fn new(short: char, name: &'static str) -> Self { 49 | Flag { short, name } 50 | } 51 | } 52 | 53 | inventory::collect!(Flag); 54 | ``` 55 | 56 | This `collect!` call must be in the same crate that defines the plugin type. 57 | This macro does not "run" anything so place it outside of any function body. 58 | 59 | ### Registering plugins 60 | 61 | Now any crate with access to the `Flag` type can register flags as a plugin. 62 | Plugins can be registered by the same crate that declares the plugin type, or by 63 | any downstream crate. 64 | 65 | ```rust 66 | inventory::submit! { 67 | Flag::new('v', "verbose") 68 | } 69 | ``` 70 | 71 | The `submit!` macro does not "run" anything so place it outside of any function 72 | body. In particular, note that all `submit!` invocations across all source files 73 | linked into your application all take effect simultaneously. A `submit!` 74 | invocation is not a statement that needs to be called from `main` in order to 75 | execute. 76 | 77 | ### Iterating over plugins 78 | 79 | The value `inventory::iter::` is an iterator with element type `&'static T` 80 | that iterates over all plugins registered of type `T`. 81 | 82 | ```rust 83 | for flag in inventory::iter:: { 84 | println!("-{}, --{}", flag.short, flag.name); 85 | } 86 | ``` 87 | 88 | There is no guarantee about the order that plugins of the same type are visited 89 | by the iterator. They may be visited in any order. 90 | 91 |
92 | 93 | ## How it works 94 | 95 | Inventory is built on runtime initialization functions similar to 96 | `__attribute__((constructor))` in C, and similar to the [`ctor`] crate. Each 97 | call to `inventory::submit!` produces a shim that evaluates the given 98 | expression and registers it into a registry of its corresponding type. This 99 | registration happens dynamically as part of life-before-main for statically 100 | linked elements. Elements brought in by a dynamically loaded library are 101 | registered at the time that dlopen occurs. 102 | 103 | [`ctor`]: https://github.com/mmastrac/rust-ctor 104 | 105 | Platform support includes Linux, macOS, iOS, FreeBSD, Android, Windows, 106 | WebAssembly, and a few others. Beyond this, other platforms will simply find 107 | that no plugins have been registered. 108 | 109 | For a different approach to plugin registration that *does not* involve 110 | life-before-main, see the [`linkme`] crate. 111 | 112 | [`linkme`]: https://github.com/dtolnay/linkme 113 | 114 |
115 | 116 | #### License 117 | 118 | 119 | Licensed under either of Apache License, Version 120 | 2.0 or MIT license at your option. 121 | 122 | 123 |
124 | 125 | 126 | Unless you explicitly state otherwise, any contribution intentionally submitted 127 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 128 | be dual licensed as above, without any additional terms or conditions. 129 | 130 | -------------------------------------------------------------------------------- /examples/flags.rs: -------------------------------------------------------------------------------- 1 | struct Flag { 2 | short: char, 3 | name: &'static str, 4 | } 5 | 6 | impl Flag { 7 | const fn new(short: char, name: &'static str) -> Self { 8 | Flag { short, name } 9 | } 10 | } 11 | 12 | inventory::submit! { 13 | Flag::new('v', "verbose") 14 | } 15 | 16 | inventory::collect!(Flag); 17 | 18 | fn main() { 19 | for flag in inventory::iter:: { 20 | println!("-{}, --{}", flag.short, flag.name); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![github]](https://github.com/dtolnay/inventory) [![crates-io]](https://crates.io/crates/inventory) [![docs-rs]](https://docs.rs/inventory) 2 | //! 3 | //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github 4 | //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust 5 | //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs 6 | //! 7 | //!
8 | //! 9 | //! **Typed distributed plugin registration.** 10 | //! 11 | //! This crate provides a way to set up a plugin registry into which plugins 12 | //! can be registered from any source file linked into your application. There 13 | //! does not need to be a central list of all the plugins. 14 | //! 15 | //! # Examples 16 | //! 17 | //! Suppose we are writing a command line flags library and want to allow any 18 | //! source file in the application to register command line flags that are 19 | //! relevant to it. 20 | //! 21 | //! This is the flag registration style used by [gflags] and is better suited 22 | //! for large scale development than maintaining a single central list of flags, 23 | //! as the central list would become an endless source of merge conflicts in an 24 | //! application developed simultaneously by thousands of developers. 25 | //! 26 | //! [gflags]: https://gflags.github.io/gflags/ 27 | //! 28 | //! ## Instantiating the plugin registry 29 | //! 30 | //! Let's use a `struct Flag` as the plugin type, which will contain the short 31 | //! name of the flag like `-v`, the full name like `--verbose`, and maybe other 32 | //! information like argument type and help text. We instantiate a plugin 33 | //! registry with an invocation of `inventory::collect!`. 34 | //! 35 | //! ``` 36 | //! pub struct Flag { 37 | //! short: char, 38 | //! name: &'static str, 39 | //! /* ... */ 40 | //! } 41 | //! 42 | //! impl Flag { 43 | //! pub const fn new(short: char, name: &'static str) -> Self { 44 | //! Flag { short, name } 45 | //! } 46 | //! } 47 | //! 48 | //! inventory::collect!(Flag); 49 | //! ``` 50 | //! 51 | //! This `collect!` call must be in the same crate that defines the plugin type. 52 | //! This macro does not "run" anything so place it outside of any function body. 53 | //! 54 | //! ## Registering plugins 55 | //! 56 | //! Now any crate with access to the `Flag` type can register flags as a plugin. 57 | //! Plugins can be registered by the same crate that declares the plugin type, 58 | //! or by any downstream crate. 59 | //! 60 | //! ``` 61 | //! # struct Flag; 62 | //! # 63 | //! # impl Flag { 64 | //! # const fn new(short: char, name: &'static str) -> Self { 65 | //! # Flag 66 | //! # } 67 | //! # } 68 | //! # 69 | //! # inventory::collect!(Flag); 70 | //! # 71 | //! inventory::submit! { 72 | //! Flag::new('v', "verbose") 73 | //! } 74 | //! # 75 | //! # fn main() {} 76 | //! ``` 77 | //! 78 | //! The `submit!` macro does not "run" anything so place it outside of any 79 | //! function body. In particular, note that all `submit!` invocations across all 80 | //! source files linked into your application all take effect simultaneously. A 81 | //! `submit!` invocation is not a statement that needs to be called from `main` 82 | //! in order to execute. 83 | //! 84 | //! ## Iterating over plugins 85 | //! 86 | //! The value `inventory::iter::` is an iterator with element type `&'static 87 | //! T` that iterates over all plugins registered of type `T`. 88 | //! 89 | //! ``` 90 | //! # struct Flag { 91 | //! # short: char, 92 | //! # name: &'static str, 93 | //! # } 94 | //! # 95 | //! # inventory::collect!(Flag); 96 | //! # 97 | //! for flag in inventory::iter:: { 98 | //! println!("-{}, --{}", flag.short, flag.name); 99 | //! } 100 | //! ``` 101 | //! 102 | //! There is no guarantee about the order that plugins of the same type are 103 | //! visited by the iterator. They may be visited in any order. 104 | //! 105 | //! ## WebAssembly and constructors 106 | //! 107 | //! `inventory` supports all WebAssembly targets, including 108 | //! `wasm*-unknown-unknown`. However, in unusual circumstances, ensuring that 109 | //! constructors run may require some extra effort. The Wasm linker will 110 | //! synthesize a function `extern "C" unsafe fn __wasm_call_ctors()` which calls 111 | //! all constructors when invoked; this function will *not* be exported from the 112 | //! module unless you do so explicitly. Depending on the result of a heuristic, 113 | //! the linker may or may not insert a call to this function from the beginning 114 | //! of every function that your module exports. Specifically, it regards a 115 | //! module as having "command-style linkage" if: 116 | //! 117 | //! * it is not relocatable; 118 | //! * it is not a position-independent executable; 119 | //! * and it does not call `__wasm_call_ctors`, directly or indirectly, from any 120 | //! exported function. 121 | //! 122 | //! The linker expects that the embedder will call into a command-style module 123 | //! only once per instantiation. Violation of this expectation can result in 124 | //! `__wasm_call_ctors` being called multiple times. This is dangerous in 125 | //! general, but safe and mostly harmless in the case of constructors generated 126 | //! by `inventory`, which are idempotent. 127 | //! 128 | //! If you are building a module which relies on constructors and may be called 129 | //! into multiple times per instance, you should export `__wasm_call_ctors` (or 130 | //! a wrapper around it) and ensure that the embedder calls it immediately after 131 | //! instantiation. Even though `inventory` may work fine without this, it is 132 | //! still good practice, because it avoids unnecessary overhead from repeated 133 | //! constructor invocation. It also can prevent unsoundness if some of your 134 | //! constructors are generated by other crates or other programming languages. 135 | //! 136 | //! ``` 137 | //! #[cfg(target_family = "wasm")] 138 | //! unsafe extern "C" { 139 | //! fn __wasm_call_ctors(); 140 | //! } 141 | //! 142 | //! fn main() { 143 | //! #[cfg(target_family = "wasm")] 144 | //! unsafe { 145 | //! __wasm_call_ctors(); 146 | //! } 147 | //! } 148 | //! ``` 149 | 150 | #![doc(html_root_url = "https://docs.rs/inventory/0.3.20")] 151 | #![no_std] 152 | #![deny(unsafe_op_in_unsafe_fn)] 153 | #![allow( 154 | clippy::doc_markdown, 155 | clippy::empty_enum, 156 | clippy::expl_impl_clone_on_copy, 157 | clippy::let_underscore_untyped, 158 | clippy::let_unit_value, 159 | clippy::must_use_candidate, 160 | clippy::new_without_default, 161 | clippy::semicolon_if_nothing_returned, // https://github.com/rust-lang/rust-clippy/issues/7324 162 | )] 163 | 164 | // Not public API. 165 | #[doc(hidden)] 166 | pub extern crate core; 167 | 168 | use core::cell::UnsafeCell; 169 | use core::marker::PhantomData; 170 | use core::ops::Deref; 171 | use core::ptr; 172 | #[cfg(target_family = "wasm")] 173 | use core::sync::atomic::AtomicBool; 174 | use core::sync::atomic::{AtomicPtr, Ordering}; 175 | 176 | // Not public API. Used by generated code. 177 | #[doc(hidden)] 178 | pub struct Registry { 179 | head: AtomicPtr, 180 | } 181 | 182 | // Not public API. Used by generated code. 183 | #[doc(hidden)] 184 | pub struct Node { 185 | pub value: &'static dyn ErasedNode, 186 | pub next: UnsafeCell>, 187 | #[cfg(target_family = "wasm")] 188 | pub initialized: AtomicBool, 189 | } 190 | 191 | // The `value` is Sync, and `next` is only mutated during submit, which is prior 192 | // to any reads. 193 | unsafe impl Sync for Node {} 194 | 195 | // Not public API. Used by generated code. 196 | #[doc(hidden)] 197 | pub trait ErasedNode: Sync { 198 | // SAFETY: requires *node.value is of type Self. 199 | unsafe fn submit(&self, node: &'static Node); 200 | } 201 | 202 | impl ErasedNode for T { 203 | unsafe fn submit(&self, node: &'static Node) { 204 | unsafe { 205 | T::registry().submit(node); 206 | } 207 | } 208 | } 209 | 210 | /// Trait bound corresponding to types that can be iterated by inventory::iter. 211 | /// 212 | /// This trait cannot be implemented manually. Instead use the [`collect`] macro 213 | /// which expands to an implementation of this trait for the given type. 214 | /// 215 | /// # Examples 216 | /// 217 | /// ``` 218 | /// use inventory::Collect; 219 | /// 220 | /// fn count_plugins() -> usize { 221 | /// inventory::iter::.into_iter().count() 222 | /// } 223 | /// ``` 224 | pub trait Collect: Sync + Sized + 'static { 225 | #[doc(hidden)] 226 | fn registry() -> &'static Registry; 227 | } 228 | 229 | impl Registry { 230 | // Not public API. Used by generated code. 231 | pub const fn new() -> Self { 232 | Registry { 233 | head: AtomicPtr::new(ptr::null_mut()), 234 | } 235 | } 236 | 237 | // SAFETY: requires type of *new.value matches the $ty surrounding the 238 | // declaration of this registry in inventory::collect macro. 239 | unsafe fn submit(&'static self, new: &'static Node) { 240 | // The WebAssembly linker uses an unreliable heuristic to determine 241 | // whether a module is a "command-style" linkage, for which it will 242 | // insert a call to `__wasm_call_ctors` at the top of every exported 243 | // function. It expects that the embedder will call into such modules 244 | // only once per instantiation. If this heuristic goes wrong, we can end 245 | // up having our constructors invoked multiple times, which without this 246 | // safeguard would lead to our registry's linked list becoming circular. 247 | // On non-Wasm platforms, this check is unnecessary, so we skip it. 248 | #[cfg(target_family = "wasm")] 249 | if new.initialized.swap(true, Ordering::Relaxed) { 250 | return; 251 | } 252 | 253 | let mut head = self.head.load(Ordering::Relaxed); 254 | loop { 255 | unsafe { 256 | *new.next.get() = head.as_ref(); 257 | } 258 | let new_ptr = new as *const Node as *mut Node; 259 | match self 260 | .head 261 | .compare_exchange(head, new_ptr, Ordering::Release, Ordering::Relaxed) 262 | { 263 | Ok(_) => return, 264 | Err(prev) => head = prev, 265 | } 266 | } 267 | } 268 | } 269 | 270 | /// An iterator over plugins registered of a given type. 271 | /// 272 | /// The value `inventory::iter::` is an iterator with element type `&'static 273 | /// T`. 274 | /// 275 | /// There is no guarantee about the order that plugins of the same type are 276 | /// visited by the iterator. They may be visited in any order. 277 | /// 278 | /// # Examples 279 | /// 280 | /// ``` 281 | /// # struct Flag { 282 | /// # short: char, 283 | /// # name: &'static str, 284 | /// # } 285 | /// # 286 | /// # inventory::collect!(Flag); 287 | /// # 288 | /// # const IGNORE: &str = stringify! { 289 | /// use my_flags::Flag; 290 | /// # }; 291 | /// 292 | /// fn main() { 293 | /// for flag in inventory::iter:: { 294 | /// println!("-{}, --{}", flag.short, flag.name); 295 | /// } 296 | /// } 297 | /// ``` 298 | /// 299 | /// Refer to the [crate level documentation](index.html) for a complete example 300 | /// of instantiating a plugin registry and submitting plugins. 301 | #[allow(non_camel_case_types)] 302 | pub type iter = private::iter; 303 | 304 | mod void_iter { 305 | enum Void {} 306 | 307 | #[repr(C, packed)] 308 | pub struct Iter([*const T; 0], Void); 309 | 310 | unsafe impl Send for Iter {} 311 | unsafe impl Sync for Iter {} 312 | } 313 | 314 | mod value_iter { 315 | #[doc(hidden)] 316 | pub use crate::private::iter::iter; 317 | } 318 | 319 | mod private { 320 | // Based on https://github.com/dtolnay/ghost 321 | #[allow(non_camel_case_types)] 322 | pub enum iter { 323 | __Phantom(crate::void_iter::Iter), 324 | iter, 325 | } 326 | 327 | #[doc(hidden)] 328 | pub use crate::value_iter::*; 329 | } 330 | 331 | #[doc(hidden)] 332 | pub use crate::private::*; 333 | 334 | const _: () = { 335 | fn into_iter() -> Iter { 336 | let head = T::registry().head.load(Ordering::Acquire); 337 | Iter { 338 | // Head pointer is always null or valid &'static Node. 339 | node: unsafe { head.as_ref() }, 340 | marker: PhantomData, 341 | } 342 | } 343 | 344 | impl IntoIterator for iter { 345 | type Item = &'static T; 346 | type IntoIter = Iter; 347 | 348 | fn into_iter(self) -> Self::IntoIter { 349 | into_iter() 350 | } 351 | } 352 | 353 | #[doc(hidden)] 354 | impl Deref for iter { 355 | type Target = fn() -> Iter; 356 | fn deref(&self) -> &Self::Target { 357 | &(into_iter as fn() -> Iter) 358 | } 359 | } 360 | 361 | pub struct Iter { 362 | node: Option<&'static Node>, 363 | marker: PhantomData, 364 | } 365 | 366 | impl Iterator for Iter { 367 | type Item = &'static T; 368 | 369 | fn next(&mut self) -> Option { 370 | let node = self.node?; 371 | unsafe { 372 | let value_ptr = (node.value as *const dyn ErasedNode).cast::(); 373 | self.node = *node.next.get(); 374 | Some(&*value_ptr) 375 | } 376 | } 377 | } 378 | 379 | impl Clone for Iter { 380 | fn clone(&self) -> Self { 381 | Self { 382 | node: self.node, 383 | marker: PhantomData, 384 | } 385 | } 386 | } 387 | }; 388 | 389 | /// Associate a plugin registry with the specified type. 390 | /// 391 | /// This call must be in the same crate that defines the plugin type. This macro 392 | /// does not "run" anything so place it outside of any function body. 393 | /// 394 | /// # Examples 395 | /// 396 | /// Suppose we are writing a command line flags library and want to allow any 397 | /// source file in the application to register command line flags that are 398 | /// relevant to it. 399 | /// 400 | /// This is the flag registration style used by [gflags] and is better suited 401 | /// for large scale development than maintaining a single central list of flags, 402 | /// as the central list would become an endless source of merge conflicts. 403 | /// 404 | /// [gflags]: https://gflags.github.io/gflags/ 405 | /// 406 | /// ``` 407 | /// pub struct Flag { 408 | /// short: char, 409 | /// name: &'static str, 410 | /// /* ... */ 411 | /// } 412 | /// 413 | /// inventory::collect!(Flag); 414 | /// ``` 415 | /// 416 | /// Refer to the [crate level documentation](index.html) for a complete example 417 | /// of submitting plugins and iterating a plugin registry. 418 | #[macro_export] 419 | macro_rules! collect { 420 | ($ty:ty) => { 421 | impl $crate::Collect for $ty { 422 | #[inline] 423 | fn registry() -> &'static $crate::Registry { 424 | static REGISTRY: $crate::Registry = $crate::Registry::new(); 425 | ®ISTRY 426 | } 427 | } 428 | }; 429 | } 430 | 431 | /// Enter an element into the plugin registry corresponding to its type. 432 | /// 433 | /// This call may be in the same crate that defines the type, or downstream in 434 | /// any crate that depends on that crate. 435 | /// 436 | /// This macro does not "run" anything so place it outside of any function body. 437 | /// In particular, note that all `submit!` invocations across all source files 438 | /// linked into your application all take effect simultaneously. A `submit!` 439 | /// invocation is not a statement that needs to be called from `main` in order 440 | /// to execute. 441 | /// 442 | /// # Examples 443 | /// 444 | /// Put `submit!` invocations outside of any function body. 445 | /// 446 | /// ``` 447 | /// # struct Flag; 448 | /// # 449 | /// # impl Flag { 450 | /// # const fn new(short: char, name: &'static str) -> Self { 451 | /// # Flag 452 | /// # } 453 | /// # } 454 | /// # 455 | /// # inventory::collect!(Flag); 456 | /// # 457 | /// inventory::submit! { 458 | /// Flag::new('v', "verbose") 459 | /// } 460 | /// # 461 | /// # fn main() {} 462 | /// ``` 463 | /// 464 | /// Do not try to invoke `submit!` from inside of a function body as it does not 465 | /// do what you want. 466 | /// 467 | /// ```compile_fail 468 | /// // Do not do this. 469 | /// fn submit_flags(has_verbose_flag: bool) { 470 | /// if has_verbose_flag { 471 | /// inventory::submit! { 472 | /// Flag::new('v', "verbose") 473 | /// } 474 | /// } 475 | /// } 476 | /// ``` 477 | /// 478 | /// Refer to the [crate level documentation](index.html) for a complete example 479 | /// of instantiating and iterating a plugin registry. 480 | #[macro_export] 481 | macro_rules! submit { 482 | ($($value:tt)*) => { 483 | $crate::__do_submit! { 484 | { $($value)* } 485 | { $($value)* } 486 | } 487 | }; 488 | } 489 | 490 | // Not public API. 491 | #[cfg(target_family = "wasm")] 492 | #[doc(hidden)] 493 | pub mod __private { 494 | #[doc(hidden)] 495 | pub use rustversion::attr; 496 | } 497 | 498 | // Not public API. 499 | #[doc(hidden)] 500 | #[macro_export] 501 | macro_rules! __do_submit { 502 | (used={ $($used:tt)+ } $($value:tt)*) => { 503 | #[allow(non_upper_case_globals)] 504 | const _: () = { 505 | static __INVENTORY: $crate::Node = $crate::Node { 506 | value: &{ $($value)* }, 507 | next: $crate::core::cell::UnsafeCell::new($crate::core::option::Option::None), 508 | #[cfg(target_family = "wasm")] 509 | initialized: $crate::core::sync::atomic::AtomicBool::new(false), 510 | }; 511 | 512 | #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")] 513 | unsafe extern "C" fn __ctor() { 514 | unsafe { $crate::ErasedNode::submit(__INVENTORY.value, &__INVENTORY) } 515 | } 516 | 517 | // Linux/ELF: https://www.exploit-db.com/papers/13234 518 | // 519 | // macOS: https://blog.timac.org/2016/0716-constructor-and-destructor-attributes/ 520 | // 521 | // Why .CRT$XCU on Windows? https://www.cnblogs.com/sunkang/archive/2011/05/24/2055635.html 522 | // 'I'=C init, 'C'=C++ init, 'P'=Pre-terminators and 'T'=Terminators 523 | $($used)+ 524 | #[cfg_attr( 525 | all( 526 | not(target_family = "wasm"), 527 | any( 528 | target_os = "linux", 529 | target_os = "android", 530 | target_os = "dragonfly", 531 | target_os = "freebsd", 532 | target_os = "haiku", 533 | target_os = "illumos", 534 | target_os = "netbsd", 535 | target_os = "openbsd", 536 | target_os = "none", 537 | ) 538 | ), 539 | link_section = ".init_array", 540 | )] 541 | #[cfg_attr( 542 | target_family = "wasm", 543 | $crate::__private::attr( 544 | any(all(stable, since(1.85)), since(2024-12-18)), 545 | link_section = ".init_array", 546 | ), 547 | )] 548 | #[cfg_attr( 549 | any(target_os = "macos", target_os = "ios"), 550 | link_section = "__DATA,__mod_init_func", 551 | )] 552 | #[cfg_attr(windows, link_section = ".CRT$XCU")] 553 | static __CTOR: unsafe extern "C" fn() = __ctor; 554 | }; 555 | }; 556 | 557 | ({ #![used($($used:tt)+)] $($value:tt)* } { $pound:tt $bang:tt $brackets:tt $($dup:tt)* }) => { 558 | $crate::__do_submit! { 559 | used={ $pound $brackets } 560 | $($value)* 561 | } 562 | }; 563 | 564 | ({ $($value:tt)* } { $($dup:tt)* }) => { 565 | $crate::__do_submit! { 566 | used={ #[used] } 567 | $($value)* 568 | } 569 | }; 570 | } 571 | -------------------------------------------------------------------------------- /tests/compiletest.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::attr(not(nightly), ignore = "requires nightly")] 2 | #[cfg_attr(miri, ignore = "incompatible with miri")] 3 | #[test] 4 | fn ui() { 5 | let t = trybuild::TestCases::new(); 6 | t.compile_fail("tests/ui/*.rs"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | pub struct Thing(pub usize); 4 | 5 | #[test] 6 | fn test_iter() { 7 | assert_eq!(0, mem::size_of::>()); 8 | assert_eq!(1, mem::align_of::>()); 9 | } 10 | -------------------------------------------------------------------------------- /tests/ui/collect-unsized.rs: -------------------------------------------------------------------------------- 1 | pub struct Unsized(str); 2 | 3 | inventory::collect!(Unsized); 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/ui/collect-unsized.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the size for values of type `str` cannot be known at compilation time 2 | --> tests/ui/collect-unsized.rs:3:21 3 | | 4 | 3 | inventory::collect!(Unsized); 5 | | ^^^^^^^ doesn't have a size known at compile-time 6 | | 7 | = help: within `Unsized`, the trait `Sized` is not implemented for `str` 8 | note: required because it appears within the type `Unsized` 9 | --> tests/ui/collect-unsized.rs:1:12 10 | | 11 | 1 | pub struct Unsized(str); 12 | | ^^^^^^^ 13 | note: required by a bound in `Collect` 14 | --> src/lib.rs 15 | | 16 | | pub trait Collect: Sync + Sized + 'static { 17 | | ^^^^^ required by this bound in `Collect` 18 | -------------------------------------------------------------------------------- /tests/ui/non-sync.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | use std::thread; 3 | 4 | struct Thing(Rc); 5 | 6 | inventory::collect!(Thing); 7 | 8 | fn clone_all() { 9 | for thing in inventory::iter:: { 10 | let _ = Rc::clone(&thing.0); 11 | } 12 | } 13 | 14 | fn main() { 15 | // It would be bad if this were allowed. These threads would race on the 16 | // nonatomic reference counts. 17 | let thread1 = thread::spawn(clone_all); 18 | let thread2 = thread::spawn(clone_all); 19 | thread1.join().unwrap(); 20 | thread2.join().unwrap(); 21 | } 22 | -------------------------------------------------------------------------------- /tests/ui/non-sync.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: `Rc` cannot be shared between threads safely 2 | --> tests/ui/non-sync.rs:6:21 3 | | 4 | 6 | inventory::collect!(Thing); 5 | | ^^^^^ `Rc` cannot be shared between threads safely 6 | | 7 | = help: within `Thing`, the trait `Sync` is not implemented for `Rc` 8 | note: required because it appears within the type `Thing` 9 | --> tests/ui/non-sync.rs:4:8 10 | | 11 | 4 | struct Thing(Rc); 12 | | ^^^^^ 13 | note: required by a bound in `Collect` 14 | --> src/lib.rs 15 | | 16 | | pub trait Collect: Sync + Sized + 'static { 17 | | ^^^^ required by this bound in `Collect` 18 | -------------------------------------------------------------------------------- /tests/ui/outer-attr.rs: -------------------------------------------------------------------------------- 1 | struct Thing; 2 | 3 | inventory::collect!(Thing); 4 | 5 | inventory::submit! { 6 | #[used(linker)] 7 | Thing 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /tests/ui/outer-attr.stderr: -------------------------------------------------------------------------------- 1 | error: attribute must be applied to a `static` variable 2 | --> tests/ui/outer-attr.rs:6:5 3 | | 4 | 6 | #[used(linker)] 5 | | ^^^^^^^^^^^^^^^ 6 | 7 | Thing 7 | | ----- but this is a expression 8 | -------------------------------------------------------------------------------- /tests/ui/submit-nonconst.rs: -------------------------------------------------------------------------------- 1 | struct Thing; 2 | 3 | impl Thing { 4 | fn new() -> Self { 5 | Thing 6 | } 7 | } 8 | 9 | inventory::collect!(Thing); 10 | 11 | inventory::submit!(Thing::new()); 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /tests/ui/submit-nonconst.stderr: -------------------------------------------------------------------------------- 1 | error[E0015]: cannot call non-const associated function `Thing::new` in statics 2 | --> tests/ui/submit-nonconst.rs:11:20 3 | | 4 | 11 | inventory::submit!(Thing::new()); 5 | | ^^^^^^^^^^^^ 6 | | 7 | = note: calls in statics are limited to constant functions, tuple structs and tuple variants 8 | = note: consider wrapping this expression in `std::sync::LazyLock::new(|| ...)` 9 | -------------------------------------------------------------------------------- /tests/ui/submit-unrecognized.rs: -------------------------------------------------------------------------------- 1 | pub struct Thing; 2 | 3 | inventory::submit!(Thing); 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/ui/submit-unrecognized.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `Thing: ErasedNode` is not satisfied 2 | --> tests/ui/submit-unrecognized.rs:3:1 3 | | 4 | 3 | inventory::submit!(Thing); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Collect` is not implemented for `Thing` 6 | | 7 | = note: required for `Thing` to implement `ErasedNode` 8 | = note: required for the cast from `&Thing` to `&'static (dyn ErasedNode + 'static)` 9 | = note: this error originates in the macro `$crate::__do_submit` which comes from the expansion of the macro `inventory::submit` (in Nightly builds, run with -Z macro-backtrace for more info) 10 | -------------------------------------------------------------------------------- /tests/ui/used-linker.rs: -------------------------------------------------------------------------------- 1 | struct Thing; 2 | 3 | inventory::collect!(Thing); 4 | 5 | inventory::submit! { 6 | #![used(linker)] 7 | Thing 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /tests/ui/used-linker.stderr: -------------------------------------------------------------------------------- 1 | error[E0658]: `#[used(linker)]` is currently unstable 2 | --> tests/ui/used-linker.rs:6:5 3 | | 4 | 6 | #![used(linker)] 5 | | ^^^^^^^^^^^^^^^^ 6 | | 7 | = note: see issue #93798 for more information 8 | = help: add `#![feature(used_with_arg)]` to the crate attributes to enable 9 | --------------------------------------------------------------------------------