ndd 0.2.1

Non De-Duplicated cell. For statics guaranteed not to share memory with any other static/const.
Documentation
ndd-0.2.1 has been yanked.

ndd (Non-De-Duplicated)

Summary

Zero-cost transparent wrapper. Use when comparing static references/slices/pointers by address. For static variables guaranteed not to share memory with any other static or const.

Problem

Rust (or, rather, LLVM) by default de-duplicates or reuses static data and its parts. For most purposes that is good: The result binary is smaller, and because of more successful cache hits the execution may be faster.

However, that is counter-productive when the code identifies/compares static data by memory address of the reference (whether a Rust reference/slice, or a pointer/pointer range). For example, an existing Rust/3rd party API may accept ("ordinary") references/slices. You may want to extend that API's protocol/behavior with signalling/special handling when the client sends in your designated static variable by reference/slice/pointer/pointer range. (Your special handler may cast such references/slices to pointers and compare them by address with core::ptr::eq().)

You don't want the client, nor the compiler/LLVM, to reuse/share the memory address of such a designated static for any other ("ordinary") static or const values/expressions. That does work out of the box when the client passes a reference/slice defined as static: (even with the default release optimizations) each static gets its own memory space. See a test src/lib.rs -> addresses_unique_between_statics().

However, it is a problem (in release mode) with ("ordinary") const values/expressions that equal in value to the designated static. Rust/LLVM uses one matching static's address for references to equal value(s) defined as const. See a test src/lib.rs -> addresses_not_unique_between_const_and_static(). And such const definitions could even be in 3rd party (innocent) code!

Solution

ndd:NonDeDuplicated uses core::cell::Cell to hold the data passed in by the user. There is no mutation and no mutation access. The only access it gives to the inner data is through shared references.

Unlike Cell (and friends), NonDeDuplicated does implement core::marker::Sync (if the inner data's type implements Send and Sync). It can safely do so, because it never provides mutable access, and it never mutates the inner data. That is similar to how std::sync::Mutex implements Sync, too.

See a test src/lib.rs -> addresses_unique_between_const_and_ndd().

Compatibility

ndd is no_std-compatible and it doesn't need heap (alloc) either. Release versions (even-numbered major versions, and not -nightly pre-releases) compile with stable Rust. (More below).

Stable is always forward compatible

ndd is planned to be always below version 1.0. That allows you to specify it as a dependency with version 0.* (which is not possible for 1.0 and higher). That will match the newest (even-numbered major) stable version (available for your Rust) automatically.

Stable and nightly

Versioning convention:

  • Even-numbered major versions (0.2, 0.4...)

    • are for stable functionality only.
    • don't use any pre-release identifier (so, nothing like 0.4-alpha).
  • Odd-numbered major versions (0.3, 0.5...)

    • are, indeed, for nightly (unstable) functionality, and need nightly Rust toolchain.

    • always contain -nightly (pre-release identifier) in their name.

    • include functionality already present in lower stable versions with the numeric version lower by (major) 1.

      So if z = x + 1 then

      • 0.x.y (stable) and
      • 0.z.y-nightly

      then 0.z.y-nightly includes all functionality already present in 0.x.y (stable).

      Examples:

      • 0.2.1 (stable) and
      • 0.3.1-nightly
        • 0.3.1-nightly includes functionality present in 0.2.1 (stable).
      • 0.2.2 (stable) and
      • 0.3.2-nightly
        • 0.3.2-nightly includes functionality present in 0.2.2 (if they get published), BUT:
      • 0.2.1 (stable) and
      • 0.3.1-nightly
        • 0.3.1-nightly will not include functionality present in 0.2.2 that was not present in 0.2.1.
  • If needed and if practical, new major versions will use the SemVer trick. See also The Cargo Book > Dependency Resolution.

    However, ndd's only exported type is ndd::NonDeDuplicated. It is a zero-cost wrapper suitable for immutable static variables. It is not intended for function parameters, local variables or as a composite type (if you do have such a use case, please get in touch).

    Since ndd::NonDeDuplicated is not being passed around, and its functions can get inlined/optimized away, there shouldn't be any big binary size/speed or usability difference if there happen to be multiple major versions of ndd in use at the same time. So SemVer trick may be unnecessary.

Rule of thumb: On stable Rust, always specify ndd with version 0.*. Then, automatically

  • you will get the newest available even-numbered major (stable) version, and
  • your libraries will work with any newer odd-numbered major (-nightly) version of ndd, too, if any dependency (direct or transitive) requires it.

Nightly

We prefer not to introduce temporary cargo features. Removing a feature later is a breaking change. And we don't want just to make such a feature no-op and let it sit around.

So, instead, any nightly-only functionality is in separate version stream(s) that always

  • use odd-numbered major version numbers (0.3.x, 0.5.x...), so if requested explicitly (rather than with 0.*), they

    • will not auto-update to/match even-numbered major (stable) versions, and
    • will not auto-update to/match higher major versions (whether odd or even) either; and
  • are pre-releases (as per The Cargo Book > Specifying Dependencies > Pre-releases and The Cargo Book > The Manifest Format > The version field) containing -nightly in their name.

    If (by accident) there were a stable version with the same (odd-numbered) major number (or the same full numeric prefix) without a pre-release identifier, a stable (non-pre-release) version will NOT match/auto-update to a pre-release version on its own. So, if your crate and its dependencies use an even-numbered major (stable) version, they will not accidentally request -nightly on their own. They would use odd-numbered major (-nightly) version only if any other crate requires it - but that's up to the consumer.

Nightly functionality

Functionality of odd-numbered major (-nightly) versions is always subject to change.

The following extra functionality is available on 0.3.1-nightly:

as_array_of_cells

ndd::NonDeDuplicated has function as_array_of_cells, similar to Rust's core::cell::Cell::as_array_of_cells (which will, hopefully, become stable in 1.91).

as_slice_of_cells

Similar to as_array_of_cells, ndd::NonDeDuplicated has function as_slice_of_cells. That can be stable with with Rust 1.88+. However, to simplify versioning, it's bundled in -nightly together with as_array_of_cells. If you need it earlier, get in touch.

const Deref and From

With nightly Rust toolchain and use of --ignore-rust-version you can get core::ops::Deref and core::convert::From implemented as const. As of mid 2025, const traits are having high traction in Rust. Hopefully this will be stable not in years, but sooner.

Quality

Checked and tested (also with MIRI):

  • cargo clippy
  • cargo test
  • cargo test --release
  • cargo +nightly miri test

Use cases

Used by hash-injector::signal.

Updates

Please subscribe for low frequency updates at #2.