inventory

repository·master·Indexed 23 days ago

https://github.com/dtolnay/inventory

A Rust crate for typed distributed plugin registration. It allows plugins to be registered from any source file linked into an application using the collect! and submit! macros, avoiding the need for a central registration list. It provides an iterator via inventory::iter::<T>() to access registered plugins and supports platforms including Linux, macOS, iOS, FreeBSD, Android, Windows, and WebAssembly.

Tokens
1.2K
Snippets
6
Records
8
Agent score
30%

What's inside inventory

  1. How inventory works and platform support

    master

    The inventory crate uses runtime initialization functions (similar to C's __attribute__((constructor)) or the ctor crate) to register plugins.

    • Registration Timing: Registration happens dynamically during the 'life-before-main' phase for statically linked elements. For dynamically loaded libraries, registration occurs when dlopen is called.
    • Platform Support: Supported on Linux, macOS, iOS, FreeBSD, Android, Windows, and WebAssembly. On unsupported platforms, the registry will simply be empty (no plugins will be registered).
  2. How distributed plugin registration works

    master

    The inventory crate enables a pattern where different parts of an application can register data or logic without a central manifest. This is achieved through three main steps:

    1. Definition & Collection: A type is defined, and inventory::collect!(Type) is called in its home crate to create a global registry.
    2. Distributed Submission: Various crates (the same crate or downstream dependencies) use inventory::submit! { ... } at the module level to add instances to that registry. This uses linker-specific sections (like .init_array on Linux or __DATA,__mod_init_func on macOS) to ensure registration happens during program startup.
    3. Consumption: The main application iterates over the registry using inventory::iter::<Type> to process all discovered plugins.

    This pattern is highly effective for large-scale projects to avoid merge conflicts in a central registration list.

  3. Handling WebAssembly constructor execution

    master

    While inventory supports WebAssembly (Wasm) targets, the Wasm linker may not automatically call the constructors required for plugin registration depending on the module's linkage style.

    If you are building a Wasm module that relies on inventory and might be instantiated multiple times, it is best practice to explicitly export and call __wasm_call_ctors immediately after instantiation to ensure all constructors run and to avoid unnecessary overhead.

    Note: inventory's internal registration is idempotent, so calling constructors multiple times is generally safe but inefficient.

    #[cfg(target_family = "wasm")]
    unsafe extern "C" {
        fn __wasm_call_ctors();
    }
    
    fn main() {
        #[cfg(target_family = "wasm")]
        unsafe {
            __wasm_call_ctors();
        }
    }
  4. Register plugins with submit!

    master

    Use the inventory::submit! macro to register an instance of a type into a registry. This can be done from any source file or crate that has access to the plugin type. Like collect!, submit! must be placed outside of any function body. All submit! calls across all linked source files take effect automatically during the application's initialization phase (before main runs).

    inventory::submit! {
        Flag::new('v', "verbose")
    }
  5. Instantiate a plugin registry with collect!

    master

    To create a registry for a specific type, use the inventory::collect! macro. This macro must be called in the same crate that defines the plugin type and must be placed outside of any function body (at the module level). It does not execute any code at runtime; it simply sets up the collection mechanism for that type.

    pub struct Flag {
        short: char,
        name: &'static str,
        /* ... */
    }
    
    impl Flag {
        pub const fn new(short: char, name: &'static str) -> Self {
            Flag { short, name }
        }
    }
    
    inventory::collect!(Flag);
  6. Iterate over registered plugins with iter

    master

    To access the registered plugins, use inventory::iter::<T>(). This returns an iterator yielding elements of type &'static T. Note that the order of iteration for plugins of the same type is not guaranteed.

    for flag in inventory::iter::<Flag> {
        println!("-{}, --{}", flag.short, flag.name);
    }
  7. Iterate over registered plugins with `inventory::iter`

    master

    To access all registered plugins of a specific type, use the inventory::iter::<T> value. This provides an iterator that yields references of type &'static T for every item submitted to the registry of type T.

    Note:

    • There is no guarantee regarding the order in which plugins are visited; they may appear in any order.
    // Assuming Flag has been collected and submitted to
    for flag in inventory::iter::<Flag> {
        println!("-{}, --{}", flag.short, flag.name);
    }