16 stable releases
Uses new Rust 2024
| new 1.5.2 | Apr 7, 2026 |
|---|---|
| 1.5.1 | Mar 23, 2026 |
| 1.5.0 | Feb 26, 2026 |
| 1.4.0 | Jan 26, 2026 |
#125 in Data structures
800 downloads per month
Used in nexus-id
720KB
13K
SLoC
nexus-ascii
Fixed-capacity ASCII strings for high-performance systems.
Overview
nexus-ascii provides stack-allocated, fixed-capacity ASCII string types optimized for trading systems and other latency-sensitive applications. Two families of types are provided: hashed types (AsciiString, AsciiText) store a precomputed 48-bit XXH3 hash in an 8-byte header for fast equality and HashMap lookups; raw types (FlatAsciiString, FlatAsciiText) are repr(transparent) over [u8; N] with no header overhead, designed for wire protocol fields where hashing is unnecessary.
Key Features
- Zero allocation after construction - Fixed capacity, stack allocated
- Precomputed hash - 48-bit XXH3 hash stored in header, computed once at construction
- Fast equality - Single 64-bit header comparison rejects most non-equal strings
- Copy semantics - All types implement
Copyfor zero-cost moves - Immutable - Strings cannot be modified after creation, guaranteeing hash validity
- nohash-hasher support - Ideal for identity hashing in HashMaps (feature-gated)
Types
| Type | Description |
|---|---|
AsciiString<N> |
Fixed-capacity ASCII string (bytes 0x01-0x7F) with precomputed hash |
AsciiText<N> |
Printable ASCII only (bytes 0x20-0x7E) with precomputed hash |
FlatAsciiString<N> |
No-header ASCII string (bytes 0x01-0x7F), repr(transparent) over [u8; N] |
FlatAsciiText<N> |
No-header printable ASCII (bytes 0x20-0x7E), repr(transparent) over [u8; N] |
AsciiStr |
Borrowed reference to ASCII data (DST) |
AsciiTextStr |
Borrowed reference to printable ASCII data (DST) |
AsciiChar |
Single ASCII character with classification methods |
AsciiStringBuilder<N> |
Mutable builder for constructing strings |
Usage
use nexus_ascii::{AsciiString, AsciiError};
// Construction
let symbol: AsciiString<32> = AsciiString::try_from("BTC-USD")?;
// Fast equality (header comparison first)
let other: AsciiString<32> = AsciiString::try_from("BTC-USD")?;
assert_eq!(symbol, other);
// Access data
assert_eq!(symbol.as_str(), "BTC-USD");
assert_eq!(symbol.len(), 7);
// Compile-time construction
const SYMBOL: AsciiString<32> = AsciiString::from_static("ETH-USD");
Type Aliases
Common capacities have convenient aliases:
use nexus_ascii::{AsciiString8, AsciiString16, AsciiString32, AsciiString64};
let short: AsciiString8 = AsciiString8::try_from("BTC")?;
let symbol: AsciiString32 = AsciiString32::try_from("BTC-USD-PERP")?;
String Operations
use nexus_ascii::AsciiString;
let symbol: AsciiString<32> = AsciiString::try_from("BTC-USD")?;
// Case-insensitive comparison
let other: AsciiString<32> = AsciiString::try_from("btc-usd")?;
assert!(symbol.eq_ignore_ascii_case(&other));
// Case conversion (returns new string)
let upper = symbol.to_ascii_uppercase();
let lower = symbol.to_ascii_lowercase();
// Validation helpers
assert!(!symbol.contains_control_chars());
assert!(symbol.is_all_printable());
nohash-hasher Support
Enable the nohash feature for optimal HashMap performance:
[dependencies]
nexus-ascii = { version = "1.5", features = ["nohash"] }
use nexus_ascii::{AsciiString, AsciiHashMap};
let mut map: AsciiHashMap<32, u64> = AsciiHashMap::default();
let key: AsciiString<32> = AsciiString::try_from("BTC-USD")?;
map.insert(key, 42);
Since AsciiString stores a precomputed 48-bit XXH3 hash in its header, using
nohash-hasher avoids redundant hash computation during HashMap lookups. The
Hash impl applies a Fibonacci multiply finalizer for optimal bucket distribution
and SIMD group filtering — matching ahash/fxhash performance at all table sizes.
Header Layout
Each AsciiString has an 8-byte header:
- Bits 0-47: XXH3 hash (lower 48 bits)
- Bits 48-63: String length
This layout ensures:
- Single 64-bit comparison for fast equality rejection
- Length accessible without touching the data buffer
- A Fibonacci multiply finalizer (
header * 0x9E3779B97F4A7C15) provides uniform bucket distribution and proper h2 control byte entropy for hashbrown's SIMD filtering
Performance
All operations are designed for predictable, low-latency performance.
Measured in CPU cycles via rdtsc, pinned to a single core:
| Operation | p50 | p99 |
|---|---|---|
| Construction (7B symbol) | 18 | 38 |
| Construction (32B, full cap) | 24 | 56 |
| Equality (same content) | 18 | 20 |
| Equality (different, header rejects) | 18 | 20 |
| HashMap get (nohash, n=1000) | 20 | 22 |
| HashMap insert new key (nohash, n=1000) | 38 | 46 |
cmp() (7B) |
16 | 20 |
eq_ignore_ascii_case() (38B) |
18 | 22 |
to_ascii_uppercase() (20B) |
18 | 20 |
See BENCHMARKS.md for the full benchmark suite.
Collision Rate
With 48 bits of hash, collision probability follows the birthday paradox:
| Unique Strings | Expected Collisions |
|---|---|
| 1 million | ~0.002 |
| 10 million | ~0.18 |
| 50 million | ~4.4 |
For typical workloads (< 1M unique strings), collisions are effectively impossible.
When to Use
Good fit:
- Trading symbols, order IDs, session tokens
- Fixed-format protocol fields
- Keys in latency-sensitive HashMaps
- Any ASCII data with known maximum length
Not ideal for:
- Variable-length text of unknown size
- UTF-8 content
- Strings that need mutation after creation
Features
| Feature | Description |
|---|---|
std (default) |
Enable std::error::Error impls and TryFrom<String> |
nohash |
Enable nohash-hasher support for identity hashing (implies std) |
serde |
Enable Serialize/Deserialize for all types |
bytes |
Enable conversion to/from bytes::Bytes (implies std) |
Serde Support
Enable the serde feature for serialization:
[dependencies]
nexus-ascii = { version = "1.5", features = ["serde"] }
use nexus_ascii::AsciiString;
let symbol: AsciiString<32> = AsciiString::try_from("BTC-USD")?;
// Serialize as string
let json = serde_json::to_string(&symbol)?; // "\"BTC-USD\""
// Deserialize with validation
let restored: AsciiString<32> = serde_json::from_str(&json)?;
Deserialization returns an error (not panic) if:
- The string exceeds capacity
- The string contains null bytes or non-ASCII bytes
- For
AsciiText/FlatAsciiText, the string contains non-printable characters
Bytes Crate Integration
Enable the bytes feature for async I/O integration:
[dependencies]
nexus-ascii = { version = "1.5", features = ["bytes"] }
use nexus_ascii::AsciiString;
use bytes::Bytes;
let symbol: AsciiString<32> = AsciiString::try_from("BTC-USD")?;
// Convert to Bytes
let b: Bytes = symbol.into();
// Convert from Bytes (with validation)
let restored: AsciiString<32> = AsciiString::try_from(b)?;
no_std Support
This crate is no_std compatible. Disable default features to use in no_std environments:
[dependencies]
nexus-ascii = { version = "1.5", default-features = false }
Note: Without std, Error trait impls and TryFrom<String> conversions are unavailable.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.