HyperUuid 0.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package HyperUuid --version 0.1.0
                    
NuGet\Install-Package HyperUuid -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="HyperUuid" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="HyperUuid" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="HyperUuid" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add HyperUuid --version 0.1.0
                    
#r "nuget: HyperUuid, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package HyperUuid@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=HyperUuid&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=HyperUuid&version=0.1.0
                    
Install as a Cake Tool

HyperUuid

UuidGenerator.NewV4() beats Guid.NewGuid() by ~5.67x — with zero heap allocation, on every version including v5 — because it calls straight into a native Rust core instead of the BCL's own managed generator.

RFC 9562 UUID v4 (random), v5 (deterministic), v6 and v7 (time-sortable) generation, calling directly into the native libhyperuuid shared library via source-generated LibraryImport P/Invoke — no runtime bridge, no reflection, AOT/trim-friendly. Ships as RID-specific native assets inside the package the standard NuGet way.

using HyperUuid;

var id = UuidGenerator.NewV4();
var id2 = UuidGenerator.NewV5(UuidGenerator.Namespaces.Dns, "example.com");
var id3 = UuidGenerator.NewV6();
var id4 = UuidGenerator.NewV7();

// Time-sortable versions round-trip their embedded timestamp:
DateTimeOffset created = UuidGenerator.V7Timestamp(id4);

// Version-agnostic: null instead of assuming id4 is v6/v7:
DateTimeOffset? maybeCreated = UuidGenerator.GetTimestamp(id4);

// Byte order SQL Server's uniqueidentifier needs on the wire to sort by creation order:
Guid sqlOrdered = UuidGenerator.V7ToSqlOrder(id4);

// Bulk generation shares one timestamp capture, one random-bytes fetch, and (v7) one
// contiguous counter reservation across the whole batch:
Guid[] batch = UuidGenerator.NewV7Batch(1000);

Returns plain System.Guid — this binding does no byte-order conversion of its own beyond the bigEndian: true Guid constructor overload (.NET 8+), since that's already the correct, direct RFC 9562 mapping. UuidGenerator.Namespaces.Dns/Url/Oid/X500 are RFC 9562 §6.6's well-known namespaces; UuidGenerator.Nil/Max are the §5.9/§5.10 special values (Nil is literally Guid.Empty). NewV6/NewV7 also accept a DateTimeOffset directly (NewV6(DateTimeOffset)), not just a raw millisecond count; GetTimestamp is the version-agnostic counterpart to V6Timestamp/V7Timestamp — it checks the version nibble itself and returns null for anything but a genuine v6/v7 Guid, instead of assuming the caller already knows.

Why not Guid.NewGuid() / Guid.CreateVersion7()?

  1. It's measurably faster, not just different. Real BenchmarkDotNet numbers, [MemoryDiagnoser], linux-arm64 (dotnet run -c Release --project ../csharp/HyperUuid.Benchmarks -- --filter *Generation*):

    Method Mean Allocated
    Guid.NewGuid() 630.26 ns 0 B
    UuidGenerator.NewV4() 111.20 ns (5.67x faster) 0 B
    UuidGenerator.NewV5() 131.31 ns (4.80x faster) 0 B
    UuidGenerator.NewV6() 75.57 ns (8.34x faster) 0 B
    UuidGenerator.NewV7() 82.10 ns (7.68x faster) 0 B

    Including NewV5(Guid, string) — it used to allocate 40 B encoding the name to UTF-8 via Encoding.UTF8.GetBytes(name); now it UTF-8-encodes into a 256-byte stack buffer with an ArrayPool fallback for longer names, the same technique the batch methods already used (and, before that, proven in this project's own SequentialGuid library). NewV5(Guid, ReadOnlySpan<byte>) remains available if you already have bytes and want to skip the encode step entirely.

  2. A real monotonic counter. Guid.CreateVersion7() implements no counter (RFC 9562 §6.2 Method 1): two BCL v7 GUIDs minted in the same millisecond sort randomly relative to each other, which is exactly the clustered-index fragmentation problem v7 adoption exists to solve. UuidGenerator.NewV7() reserves a slot in a process-global counter every call, guaranteeing strict creation order under concurrency — verified across interleaved individual and batch calls in this project's own test suite, not just in isolation.

  3. v6, which the BCL doesn't have at all. A field-compatible reordering of v1 for the same sort/index locality as v7, useful when you're migrating off legacy v1 IDs. No Guid.CreateVersion6 exists anywhere in the BCL.

  4. Batch generation. NewV7Batch(1000) is ~3.9x faster than 1000 individual NewV7() calls (24 µs vs 93 µs) — one native call, one random-bytes fetch, one counter reservation for the whole batch, instead of paying per-item overhead a thousand times. Guid.NewGuid()/CreateVersion7() have no bulk API; you'd write that loop yourself.

  5. Cross-language consistency. The exact same Rust core also mints v5 namespace UUIDs for Ruby, Python, Go, and every other binding in this repo — verified in CI to match Python's own uuid.uuid5 byte-for-byte. If your system isn't C#-only, that's not something the BCL can offer at all.

  6. SQL Server byte ordering, for free. UuidGenerator.V7ToSqlOrder(id4) converts a version 7 UUID to the byte order System.Data.SqlTypes.SqlGuid comparison — and therefore T-SQL ORDER BY on a uniqueidentifier column — needs to sort by creation order (V6ToSqlOrder does the same for version 6), the same permutation this project's own SequentialGuid/Svartalfheim already use. Verified directly against the real SqlGuid comparator in this package's own test suite, not a hand-rolled stand-in — and it's the same native function every other binding in this repo calls, not a C#-only reimplementation. Neither Guid.NewGuid() nor Guid.CreateVersion7() has any such concept.

The honest trade-off: this is a native dependency (a platform-specific libhyperuuid.so/.dylib/.dll bundled per-RID) instead of a BCL type that's always just there. If you only need plain v4 randomness in a C#-only codebase, Guid.NewGuid() is simpler and that's a completely reasonable choice.

AOT

Publishes cleanly under PublishAot — LibraryImport is source-generated with no runtime reflection anywhere in this assembly, and the project opts into (and fails the build on) the trim/Native-AOT analyzers via IsAotCompatible. See HyperUuid.AotSmokeTest/ for a minimal AOT-published console app exercising it.

WebAssembly (Blazor)

Works from a plain <PackageReference Include="HyperUuid" /> — no <NativeFileReference>, no hand-written P/Invoke, no bridging in your own Rust build. Verified for real: a fresh Blazor WebAssembly app with nothing but that one package reference, run in an actual headless Chromium session, correctly generates v4s, matches the RFC 9562 v5 vector, and embeds a real-clock v7 timestamp that matches the actual wall clock exactly (zero drift) — not just "it builds."

This is the real reason this package's floor is net10.0, not net8.0. Not a preference — net8.0's WASM P/Invoke-table generator (ManagedToNativeGenerator, part of dotnet/runtime) scans the wrong copy of a multi-RID-asset package: the default lib/{tfm}/ assembly, never the runtimes/browser-wasm/lib/{tfm}/ one that actually ships and runs. Confirmed by inspecting the generated pinvoke-table.h directly: on net8.0 it registered this package's functions under module "hyperuuid", never "*", even though the deployed assembly was verified byte-for-byte identical to the correct browser build — so the app crashes with DllNotFoundException("*") at runtime, unconditionally, with no workaround found on the package side (redeclaring the P/Invoke directly in the consumer's own entry assembly doesn't avoid it either — same failure). Re-ran the identical repro against .NET 10 and it's fixed there: the generator correctly scans the browser assembly and the app runs end to end in a real headless-Chromium session.

How: one compiled assembly covers every platform, including browser-wasm — no separate build. Every native entry point is declared twice, unconditionally, sharing the same underlying C symbol: once against "hyperuuid" (resolved via dlopen on every real native platform), once against "*" (a statically-linked WASM native has no separate "hyperuuid" module to open, since its functions are already part of the same dotnet.native.wasm the app itself runs in; "*" resolves against the current module instead). OperatingSystem.IsBrowser() picks the right one at each call site — a real runtime check the .NET linker specifically knows how to constant-fold per publish target (the same mechanism the BCL itself uses for platform-conditional code), so a trimmed/published build still only ships the branch that platform can actually reach. Only the Rust core's wasm32-unknown-emscripten static library (cargo rustc --crate-type staticlib — the default cdylib produces an already-linked module NativeFileReference can't pull symbols from) is genuinely RID-specific, landing under runtimes/browser-wasm/nativeassets/net10.0/ in the .nupkg; the managed assembly itself needs no RID-specific copy anymore, confirmed by inspecting a real self-contained dotnet publish -r <rid> output too: no WASM files leak into a non-WASM deployment.

Building this exact source once instead of twice also turned out to matter beyond simplicity: two independent, from-scratch dotnet pack runs now produce a byte-identical managed assembly (verified with a real checksum comparison, not assumed) — the earlier two-builds-sharing-one-obj/-directory design never gave that guarantee, and is the leading suspect for this package's NuGet health-check failures on every release before this one.

The one piece that doesn't auto-wire — NuGet's runtimes/{rid}/nativeassets/{tfm}/ convention is real, and the WASM SDK really does auto-promote a resolved NativeLibrary item into NativeFileReference, but restore doesn't actually populate NativeLibrary from a plain PackageReference's nativeassets folder the way it does native/ for ordinary P/Invoke (confirmed empirically, not assumed) — is supplied by this package's own build/net10.0/HyperUuid.targets, auto-imported into every consuming project via NuGet's standard convention, which adds both the NativeFileReference and (also confirmed necessary the hard way — linking the code in alone does not make it resolvable via "*" at runtime) an explicit EmccExportedFunction entry per native function this package P/Invokes. That's the actual mechanism making this "just works" for real, not a hopeful description of how NuGet packaging is supposed to behave.

A real, currently-open upstream blocker you'll still hit, regardless of TFM: the packaging above eliminates bringing your own Rust build and P/Invoke shim — it does not eliminate a genuine bug in the consumer's own .NET SDK. The moment any Blazor build does a native relink — which this package's own auto-injected NativeFileReference triggers just as surely as a hand-written one would — Unknown option '--enable-bulk-memory-opt' fires: a real Emscripten/rustc version-skew bug in the wasm-tools workload (rustc >= 1.87's WASM target-feature metadata vs. Binaryen wasm-opt builds older than Emscripten 3.1.74, which every wasm-tools SDK band bundles as of this writing — confirmed still true on .NET 10 and .NET 11 preview both, not just 8), confirmed identically on linux-arm64 and linux-x64, and independently in an unrelated project (PyO3/maturin#2549). Filed upstream with a minimal repro and a verified workaround: dotnet/runtime#132858. Until that lands, every consumer building a Blazor app that references this package needs the same one-line fix — swap the SDK's bundled wasm-opt (~/.dotnet/packs/Microsoft.NET.Runtime.Emscripten.<version>.Sdk.<rid>/<pack-version>/tools/bin/wasm-opt) for a newer one (e.g. from a standalone emsdk install latest) — nothing this package can fix from the inside.

Install

Published to nuget.org — no extra package source needed:

dotnet add package HyperUuid

Targets net10.0 — see the WebAssembly section above for why that floor exists.

See the repo root README for the full RFC 9562 coverage table and the state of every other language binding.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.9.0 124 10/20/2026
0.8.0 228 10/9/2026
0.7.0 114 10/7/2026
0.6.1 120 10/3/2026
0.6.0 118 10/3/2026
0.5.0 125 10/2/2026
0.4.0 117 10/2/2026
0.3.0 137 9/4/2026
0.2.1 126 9/2/2026
0.2.0 122 9/2/2026
0.1.1 111 8/31/2026
0.1.0 110 8/30/2026