Performance First
Every operation an interpreter runs per instruction is sub-nanosecond. Latest local Criterion means (cargo bench --bench bench, Linux x86_64 / WSL2, Rust stable, release build):
| Operation | Time |
|---|---|
Construct nil |
0.09 ns |
Construct int / bool |
0.20 ns |
Construct float |
0.31 ns |
is_int (kind test) |
0.20 ns |
as_int (read payload) |
0.29 ns |
unpack (per value) |
0.32 ns |
== (two ints) |
0.34 ns |
Size of Value |
8 bytes |
Numbers vary by CPU and environment; run the suite on your target to establish a baseline. A Value is Copy and register-sized, so passing one costs the same as passing a u64.
Features
- One 8-byte type —
nil,bool,i32,f64, and internedSymbol, all in a singleCopyword. - NaN-boxed — real floats stored as themselves; every other kind hidden in spare quiet-NaN payloads.
- Fully safe — no
unsafe, no pointers, no raw transmutes.#![forbid(unsafe_code)]. no_std, noalloc— pure integer and float arithmetic; runs anywhere.- Ergonomic and compact — build with
Value::int(…), test withis_*, read withas_*, andmatchon theUnpackedview when you need every kind at once. serdeready — opt-inSerialize/DeserializeforValue.- Property-tested invariants — round-trip and kind-exclusivity checked across randomized inputs with
proptest.
Installation
[]
= "0.2"
# With serde support:
= { = "0.2", = ["serde"] }
# no_std (drops the std forwarding to intern-lang):
= { = "0.2", = false }
MSRV is 1.85+ (Rust 2024 edition).
Quick Start
use ;
// One eight-byte type for every runtime value.
let stack = ;
// Test a single kind and read it back.
assert!;
assert_eq!;
assert_eq!;
// Or match on every kind at once.
assert_eq!;
// A Value is register-sized and Copy.
assert_eq!;
Interned names
Strings and identifiers are carried as Symbol handles. value-lang stores the handle; the interner owns the bytes.
use Interner;
use Value;
let mut interner = new;
let name = interner.intern;
let v = sym;
assert_eq!;
assert_eq!;
How NaN-boxing works
A 64-bit IEEE-754 double is sign(1) | exponent(11) | mantissa(52). A value is boxed — not a real float — when the exponent is all ones and the top two mantissa bits are set. No finite double or infinity matches that pattern, and every genuine NaN is folded onto one canonical encoding on the way in, so the boxed space belongs to value-lang alone.
Within a boxed word, a fixed quiet-NaN header sits in the high bits, a small kind tag names the kind, and the low 32 bits hold the payload: an i32 bit-for-bit, or a Symbol's non-zero id. Decoding is one mask to check the header and a compare on the tag. There are no branches on the copy path and no heap indirection.
Because floats keep IEEE-754 semantics, equality does too: two Float values compare with f64 rules — so NaN != NaN and 0.0 == -0.0 — while every other kind compares by bit pattern. Value therefore does not implement Eq/Hash; see docs/API.md for the details and how to build a key.
API Overview
For the complete reference with examples, see docs/API.md.
Value— the eight-byte NaN-boxed value; constructors,is_*predicates,as_*accessors,bits,unpack.Unpacked— the tagged-union view for exhaustivematch.Symbol— the interned string handle aValuecan carry (re-exported fromintern-lang).
Feature Flags
| Feature | Default | Description |
|---|---|---|
std |
✅ | Forwards std to intern-lang. The crate itself is no_std. |
serde |
❌ | Serialize / Deserialize for Value (symbols as their raw id). |
Testing
The property suite in tests/roundtrip.rs checks the core invariants — pack/unpack is the identity, every f64 bit pattern classifies as a float, and the kinds are mutually exclusive — across randomized inputs.
Cross-Platform Support
The representation is pure arithmetic with no platform-specific code, so it behaves identically everywhere Rust runs. CI covers Linux, macOS, and Windows on both stable and the 1.85 MSRV.
Contributing
See REPS.md for the engineering standards and the definition of done. Before a PR: cargo fmt --all, cargo clippy --all-targets --all-features -- -D warnings, and cargo test --all-features must be clean.