# Linkme: registering static values across crates without runtime initialisation

> Linkme gives Rust a distributed slice, a static collection that any crate in the dependency graph can add elements to, and it does it at link time rather than through constructors. That makes it useful for plugin style registries and for no_std builds, at the cost of requiring platform specific linker support and a matching element type.

**dtolnay/linkme** — Safe cross-platform linker shenanigans

- Repository: https://github.com/dtolnay/linkme
- Stars: 910 · Forks: 47
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/dtolnay-linkme

## Linkme fills a slice during linking, not during start up

The central idea of linkme is that a static collection can be populated by any crate in the dependency graph, and the population happens while the binary is being linked rather than while the program is starting.

The project describes this as a distributed slice: a collection of static elements gathered into a contiguous section of the binary by the linker, with elements definable from anywhere in the dependency graph of the final binary. The implementation is based on link_section attributes and platform specific linker support, and the project states plainly that it does not involve life-before-main or any other runtime initialisation on any platform. The description calls it a zero-cost safe abstraction that operates entirely during compilation and linking.

That last part is the reason to care. The usual Rust trick for a global registry is to have each participating crate expose a function that registers itself into a global map, and to call those functions from a list that someone has to maintain by hand. Linkme removes both the list and the call. The cost is that the collection is fixed once the binary is linked: you cannot add a slice element at runtime, and the values are resolved by the linker, so anything that depends on ordering, on a runtime value, or on being able to unregister is outside what this crate can do.

The documentation is careful about a second limitation, which is that the element type has to match. A distributed slice is declared as a static of type [T] for some T, and every element registered into it must have exactly that type, or the program will not compile.

## Declaring the slice and registering an element

The API surface is two attributes. Declaring the slice is a single attribute on a static item whose type is a slice of some element type:

```rust
use linkme::distributed_slice;

#[distributed_slice]
pub static BENCHMARKS: [fn(&mut Bencher)];
```

Registering an element is the same attribute with the path to the slice in parentheses. The initializer has to be a const expression:

```rust
use linkme::distributed_slice;
use other_crate::BENCHMARKS;

#[distributed_slice(BENCHMARKS)]
static BENCH_DESERIALIZE: fn(&mut Bencher) = bench_deserialize;

fn bench_deserialize(b: &mut Bencher) {
    /* ... */
}
```

Elements can live in the same crate that declares the slice or in any downstream crate, and the documentation states that elements across all crates linked into the final binary will be observed to be present in the slice at runtime.

The result behaves like an ordinary static slice reference, which is the ergonomic point. You iterate it, index it, slice it, and call len on it:

```rust
fn main() {
    // Iterate the elements.
    for bench in BENCHMARKS {
        /* ... */
    }

    // Index into the elements.
    let first = BENCHMARKS[0];

    // Slice the elements.
    let except_first = &BENCHMARKS[1..];

    // Invoke methods on the underlying slice.
    let len = BENCHMARKS.len();
}
```

Type checking is strict, and the documentation shows the compiler error you get when an element does not match the declared type, an expected fn pointer against a found usize. That failure mode is a good property of the design, since a mistyped registration is caught at compile time rather than becoming a misdispatch at run time.

## Applying the attribute to a function is the common shorthand

The most common use of a distributed slice is a collection of function pointers, and linkme provides a shorthand for that case. Instead of writing a static that holds a pointer to the function, you apply the attribute directly to the function definition, and the attribute expands to an anonymous static holding the pointer:

```rust
use linkme::distributed_slice;

#[distributed_slice]
pub static BENCHMARKS: [fn(&mut Bencher)];

// Equivalent to:
//
//    #[distributed_slice(BENCHMARKS)]
//    static _: fn(&mut Bencher) = bench_deserialize;
//
#[distributed_slice(BENCHMARKS)]
fn bench_deserialize(b: &mut Bencher) {
    /* ... */
}
```

Note that the declaration and the registration carry the same attribute name and are distinguished by their shape, one on a static with a slice type, one on a function. That is a small ergonomic wrinkle, since a reader has to look at the item rather than the attribute to tell which role it plays, but it keeps the common case down to one line per contributor.

For a crate author who wants a benchmark harness, a test registry, or a plugin table, this is the whole workflow: one static in the library, one attribute per contributor, and no central list to update when a new crate joins. The cost of that convenience is that registration is invisible to tooling that only reads source files, since nothing in the type system records the relationship between a function and the slice it joins. A compiler error will catch a wrong type, but a missing or duplicate registration is not a compile time error, and a stray entry in the slice is invisible until runtime.

## What the crate depends on, and what it asks of your toolchain

The package metadata is small and gives a clear picture of the constraints. The crate is edition 2021 with a rust-version of 1.71, it is categorised under no_std and no_std::no-alloc, and the license field reads MIT OR Apache-2.0.

```toml
[dependencies]
linkme = "0.3"
```

The dependency list is one entry, linkme-impl pinned to the exact same version and resolved from the impl directory in the same repository, which confirms that the procedural macro and the runtime support crate are released in lockstep. There is a build.rs at the top level of the repository, and a feature named used_linker that forwards to the same feature on linkme-impl, which suggests the crate generates and installs a linker script and that this feature controls how that script is applied. The workspace members are impl, tests/cortex and tests/crate, so the test suite covers a cortex target and a separate crate fixture rather than only the host platform.

The dev-dependencies include once_cell, rustversion and trybuild, and trybuild is the one that matters for understanding the quality of the crate. Trybuild is the standard way to test compiler error messages, which is consistent with the documentation choosing to show the exact E0308 diagnostic rather than describing it. The rustversion dependency suggests the test suite is aware of which Rust versions are in play, which is a sensible guard for a crate that manipulates linker behaviour.

The repository also holds a build.rs, an impl directory, a src directory and a tests directory, alongside the usual Cargo.toml, README and licence files. There is no benchmark harness or example directory, and there is little configuration surface to tune.

## The platform table is the real limitation

The README opens with a support matrix, and the honest way to read it is that the crate is a wrapper around platform specific linker features. The distributed slice row carries a green mark for Linux, macOS, Windows, FreeBSD, OpenBSD and illumos, and the note under the table says that pull requests adding support for other platforms are welcome. The Other column is empty, which is the one cell that matters when you are planning a port to an unusual target.

The requirement is architectural rather than incidental. A distributed slice needs a custom section in the output, and it needs the linker to gather the contributions from many object files into one contiguous region, and it needs a way for the program to locate the start and the length of that region at run time. Those three things are done differently, and in some cases only with linker flags, on each platform. The crate's job is to hide that, and it can only hide it where the platform supports it.

The no_std and no-alloc categories point in the same direction. The crate is built so that the resulting slice lives in static memory with no allocator and no constructor, which is why it is a good fit for embedded and for code that must run before the runtime starts. The reverse side is that it depends on the toolchain cooperating in ways a pure Rust library never would, and the build.rs and the used_linker feature are the visible cost of that dependency. A project targeting an operating system outside the table should treat the absence of a green mark as a blocker rather than a caveat.

## Licence, releases, and the state of the repository

Linkme is dual licensed under the Apache License, Version 2.0 or the MIT license at your option, and the package metadata records the same choice in the license field as MIT OR Apache-2.0. The repository carries both licence files, LICENSE-APACHE and LICENSE-MIT, and the contribution note states that unless you explicitly say otherwise, any contribution submitted for inclusion is dual licensed the same way with no additional terms.

For a crate this small, that is a permissive position with no strings attached. The Apache 2.0 option matters mostly for organisations whose own policies require an explicit patent grant, and MIT is the simpler choice for permissive compliance. Neither obligation is unusual for a Rust dependency, and there is no copyleft clause to reason about when you vendor it into a proprietary binary.

On the maintenance side, the last push to the repository was on 2026-08-05, and the recent releases are 0.3.37 on 2026-07-18, 0.3.36 on 2026-04-17 and 0.3.35 on 2025-09-28. The version numbering carries a 0.3 prefix, so the crate has not reached a 1.0 release, and the pin in the dependency is an exact version for the internal linkme-impl crate rather than a range, which is the sort of detail that tells you the macro and the runtime halves must match exactly. The project is stable in the sense that its releases are patch level increments with no interface churn visible in the documentation, but the 0.3 line is still pre-1.0, so expect the occasional breaking change in a minor release.

## Conclusion

Linkme fits a Rust codebase that needs a registry assembled from separate crates, especially one that targets no_std or cannot afford runtime initialisation, and it is a fair replacement for a hand rolled inventory list of registration functions. It is a poor fit if your platforms include a target without linker support for custom sections, if you need elements to be added after start up, or if you want dynamic unloading. Before adopting it, check that the distributed slice element type is identical in every contributing crate, since a mismatch is a compile error rather than a runtime surprise, and confirm on each target you ship that the linker script the crate generates is actually being used, because the crate documents a used_linker feature for exactly that situation.

## FAQ

### What is linkme used for in Rust?

Linkme provides a distributed slice, a static collection whose elements can be registered from any crate in the dependency graph of the final binary. Registration happens at link time through link_section attributes, with no runtime initialisation on any platform.

### How do I add linkme to a Rust project?

Add the dependency to Cargo.toml, which the README shows as linkme = "0.3". The crate requires Rust 1.71 or newer and resolves one internal dependency, linkme-impl, at an exact matching version.

### Does linkme work without the standard library?

Yes. The package is categorised under no_std and no_std::no-alloc, and the distributed slice is populated entirely at link time, so no allocator and no runtime constructor are needed.

### Which platforms does linkme support?

The README support matrix lists Linux, macOS, Windows, FreeBSD, OpenBSD and illumos. The project welcomes pull requests adding support for platforms not in the list.

### What happens if a linkme element has the wrong type?

The program does not compile. The documentation shows the compiler error for registering a usize into a slice of fn pointers, so a mismatched element is caught at compile time.

## Sources

- [dtolnay/linkme on GitHub](https://github.com/dtolnay/linkme)
- [Issues](https://github.com/dtolnay/linkme/issues)
- [License: Apache-2.0](https://github.com/dtolnay/linkme/blob/master/LICENSE)
- [README](https://github.com/dtolnay/linkme/blob/master/README.md)
- [Releases](https://github.com/dtolnay/linkme/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dtolnay-linkme
