CLI tool
dtolnay/inventory avatar
dtolnay/inventory

inventory: Typed Plugin Registration in Rust

Typed distributed plugin registration

1,363 stars61 forksRustApache-2.0

At a glance

What is it?
The inventory crate lets Rust applications collect typed plugin registrations declared across any linked crate or source file. It is useful when maintaining a central registry would create excessive coordination overhead, but its unordered, initialization-driven design does not fit every plugin system.
Who is it for?
Adopt inventory when linked Rust components need to register typed values without updating a central list, and verify the target platform, linking model, and initialization timing first. Do not choose it when registration order or execution before main is unacceptable.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 15 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why inventory avoids a central Rust plugin list

inventory addresses Rust applications in which declarations naturally belong near the code that owns them. Its README uses command-line flags as the concrete case: each component can register the flags it understands without editing a shared list. That matters when many developers or crates contribute flags, because a central registration file concentrates additions and increases the chance of merge conflicts.

The crate distributes plugin registration across the final program. The plugin type's crate creates a typed registry with `inventory::collect!`. Any crate that can access that type can then submit values with `inventory::submit!`. Iteration through `inventory::iter::<T>` exposes registered values as `&'static T`.

This design separates declaration from a central runtime call. It does not require plugin authors to call a registration function from `main`, and it does not make the defining crate maintain a list of every possible plugin. The trade-off is control. Registration depends on what is linked into the executable or loaded library, and iteration order is explicitly unspecified.

The collect, submit, and iter registration path

The API has three project-specific roles. `collect!` establishes the registry for one plugin type, `submit!` places a value into that registry, and `iter::<T>` reads the collected entries. The `collect!` invocation must live in the crate that defines the plugin type and must be outside a function body.

Registration is static in intent but dynamic in its resulting effect. The README says that `submit!` does not run where it appears as ordinary executable code. Every applicable `submit!` invocation across the linked application takes effect simultaneously before main for statically linked elements. If a dynamically loaded library contributes entries, they are registered when `dlopen` occurs.

The iterator returns references with static lifetime. The repository does not promise any ordering for values of the same type. Code that assigns meaning based on precedence, command-line syntax, or precedence-like behavior cannot depend on sequence from `inventory::iter::<T>` unless it performs an additional, explicit ordering step.

Installing inventory 0.3 in a Rust application

The README gives the dependency declaration rather than an application-specific command. Add version `0.3` under `[dependencies]` in the relevant crate's `Cargo.toml`. The package metadata identifies the crate as `inventory`, uses Rust 2021, and declares a minimum Rust version of 1.68.

toml
[dependencies]
inventory = "0.3"

This declaration makes the crate available to that package. It does not create a registry or register plugins by itself. The next section defines a flag type and its registry in the same crate. This location matters because the documentation explicitly requires `collect!` to be in the crate that defines the plugin type.

Defining and populating a Flag registry

For a first use, define a plugin type that callers can construct. This example keeps a short name and full name, then declares the registry at module scope. The README places `collect!` outside any function because it does not perform registration at that source statement.

rust
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);

A crate with access to `Flag` can submit an entry at module scope. The following declaration comes directly from the documented plugin-registration form:

rust
inventory::submit! {
    Flag::new('v', "verbose")
}

Do not wrap this declaration in a function and expect calling that function to trigger registration. According to the README, submission applies as part of the program's initialization behavior for statically linked entries. If this submission belongs to a downstream crate, that crate must be linked into the application and must have access to the public plugin type and constructor.

Reading registered flags without relying on order

Iteration is synchronous and typed. The generic argument selects the registry created for that type, while each yielded item is a `&'static Flag`. A minimal display loop follows the README example.

rust
for flag in inventory::iter::<Flag> {
    println!("-{}, --{}", flag.short, flag.name);
}

The output for the documented declaration contains a short `-v` and long `--verbose`, but the repository does not state that this entry appears first or in any other fixed position. Multiple submitters may target the same type from unrelated source files, and their visitation order is not guaranteed.

That behavior is suitable for constructing a set of available flags when sequence has no meaning. It is unsuitable when one flag must override another or when consumer order changes behavior. The crate supplies collection and typed access, not the policy needed to resolve those concerns.

Runtime initialization and dynamic-library boundaries

The mechanism is based on runtime initialization functions resembling `__attribute__((constructor))` in C. Each `submit!` generates a shim that evaluates its expression and registers the result with the corresponding typed registry. For statically linked elements, this happens during life-before-main.

Dynamic libraries introduce a different boundary. Elements brought in through a dynamically loaded library are registered at the time `dlopen` occurs. Programs that expect every plugin to exist immediately after loading the executable should account for this distinction. A registry lookup performed before the relevant `dlopen` does not have a documented mechanism in the repository for forcing later library registrations to appear retroactively.

Platform support is also part of the behavior. The README lists Linux, macOS, iOS, FreeBSD, Android, Windows, WebAssembly, and several others. On other platforms, the README says that no plugins will be registered. That is a hard adoption check for portable applications because adding the dependency can still compile without producing the expected inventory.

When inventory is the wrong registration mechanism

The first unsuitable case is order-sensitive registration. The iterator may visit entries of the same type in any order, so inventory cannot be treated as an ordered configuration mechanism. If conflict resolution depends on sequence, the application must collect the entries and apply its own explicit policy.

The second case is a strict objection to initialization before main. inventory deliberately uses constructor-like functions, and the README presents it as distinct from `linkme`, which is described as an approach that does not involve life-before-main. Programs that require all registration to occur through a chosen runtime phase should review that boundary before adopting this crate.

A third case is unsupported linkage or platform behavior. A submitted item only becomes available when its source is linked into the application or introduced through the documented dynamic-loading path. Merely existing in a crate that is not part of the final program does not populate the registry. On platforms outside the documented support set, the expected result is an empty inventory.

There is also no documented deduplication guarantee. The README describes every applicable `submit!` as producing registration behavior, but it does not define collision handling when two declarations create equal values or when multiple entries share the same flag name.

Maintenance, releases, and licensing for inventory

The repository is not archived. Its last push was on 2026-10-07, and release 0.3.25 was published on 2026-10-07. Earlier listed releases include 0.3.24 on 2026-03-30 and 0.3.23 on 2026-03-28. This supports treating the current 0.3 line as recently changed, although the README does not publish a compatibility policy or upgrade procedure.

The declared dependency range `0.3` stays within the documented 0.3 release family. Lockfile behavior, minimum supported compiler constraints for upgrades, and a documented migration path are not described in the README. Rust-version metadata currently states 1.68, while the package uses edition 2021.

The package metadata says `MIT OR Apache-2.0`, and the README presents both licenses as alternatives. Unless a contribution explicitly states otherwise, the README says that contribution is dual licensed under those terms without additional conditions. This is a licensing summary, not an opinion about legal consequences; consumers still need to review the repository's license files and their own obligations.

Editorial conclusion

Adopt inventory when linked Rust components need to register typed values without updating a central list, and verify the target platform, linking model, and initialization timing first. Do not choose it when registration order or execution before main is unacceptable. For applications that require those restrictions, the README points to linkme as the alternative approach.

Frequently asked questions

How do I create and iterate an inventory registry in Rust?

Declare `inventory::collect!(T)` in the crate that defines the plugin type `T`, submit values with `inventory::submit!`, and read them through `inventory::iter::<T>`. The registry and submission declarations belong outside function bodies.

Does inventory guarantee plugin iteration order?

No. The README says plugins of the same type may be visited in any order, so consumer code cannot depend on declaration or submission sequence.

What happens when a dynamically loaded library contributes inventory plugins?

The README states that plugins brought in by a dynamically loaded library are registered when `dlopen` occurs. Statically linked elements are registered during life-before-main.

Which Rust version and license does inventory declare?

The package metadata declares Rust version 1.68 and uses edition 2021. Its license expression is `MIT OR Apache-2.0`.

Official sources

  1. dtolnay/inventory on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dtolnay-inventory.svg)](https://hysenlabs.com/projects/dtolnay-inventory)

Was this useful?