# value-lang v0.2.0 — The Core Value
**The representation the crate exists for.** v0.2.0 lands `Value`: a runtime value packed into a single eight-byte word by NaN-boxing, plus its tagged-union view `Unpacked` and the interned `Symbol` handle it can carry. Fully safe, `no_std`, no allocator, sub-nanosecond on every operation. This is the hard part of the roadmap, and it is not deferred.
## What is value-lang?
A compact runtime value type for interpreted languages. A dynamically-typed interpreter needs one type to stand for every runtime value — `nil`, a boolean, a number, an interned name — and it copies that type on nearly every instruction. value-lang folds all of them into one 64-bit `Value` that is `Copy`, register-sized, and free of any discriminant word, because the kind lives in the bit pattern itself.
## What's new in 0.2.0
### `Value` — one eight-byte word for every runtime value
NaN-boxing exploits the fact that IEEE-754 leaves a large block of `f64` bit patterns unused: every quiet-NaN encoding names the same abstract "not a number". A `Value` stores a real float as itself and hides every other kind — `nil`, booleans, 32-bit integers, interned symbols — inside those spare NaN payloads.
```rust
use value_lang::Value;
let count = Value::int(42);
let ratio = Value::float(0.375);
let flag = Value::bool(true);
let nothing = Value::nil();
assert!(count.is_int());
assert_eq!(count.as_int(), Some(42));
assert_eq!(ratio.as_float(), Some(0.375));
// Register-sized and Copy.
assert_eq!(core::mem::size_of::<Value>(), 8);
```
Build with `nil` / `bool` / `int` / `float` / `sym`; test one kind with the `is_*` predicates; read the payload back with the `as_*` accessors, which return `Option` and never coerce across kinds. `nil`, `bool`, and `int` are `const fn`.
The encoding is entirely safe — `f64::to_bits`, `f64::from_bits`, and integer arithmetic, with no pointers and no `unsafe`. `unsafe` is forbidden crate-wide.
### `Unpacked` — the tagged-union view
When an interpreter dispatches on the kind of an operand, it wants one exhaustive `match`. `Value::unpack` expands a value into `Unpacked`, and `Value::from` packs it back; the two are duals.
```rust
use value_lang::{Unpacked, Value};
fn add(a: Value, b: Value) -> Option<Value> {
match (a.unpack(), b.unpack()) {
(Unpacked::Int(x), Unpacked::Int(y)) => Some(Value::int(x + y)),
_ => None,
}
}
assert_eq!(add(Value::int(2), Value::int(3)), Some(Value::int(5)));
assert_eq!(add(Value::int(2), Value::nil()), None);
```
### `Symbol` — interned names, packed into the tag
Strings and identifiers travel as `Symbol` handles from [`intern-lang`](https://crates.io/crates/intern-lang), re-exported at the crate root. A symbol's non-zero 32-bit id packs directly into the value, so a name in a `Value` costs four bytes and compares as an integer. This is the roadmap's "wires intern" milestone.
```rust
use intern_lang::Interner;
use value_lang::Value;
let mut interner = Interner::new();
let name = interner.intern("total");
let v = Value::sym(name);
assert_eq!(v.as_sym(), Some(name));
assert_eq!(interner.resolve(v.as_sym().unwrap()), Some("total"));
```
### IEEE-754-faithful equality
Floats keep their semantics, so equality does too: two `Float` values compare with `f64` rules — `NaN != NaN`, `0.0 == -0.0` — while every other kind compares by bit pattern, and distinct kinds are never equal (`Value::int(1) != Value::float(1.0)`). Because `NaN != NaN`, `Value` deliberately does not implement `Eq`/`Hash`; the `bits` accessor and `unpack` give you what you need to build a key when you want one.
### Optional `serde` support
Behind the `serde` feature, `Value` serializes through its kinds as an externally-tagged enum. Symbols are written as their raw id — the only part that survives without the issuing interner — and a `0` id is rejected on deserialize.
```rust
# #[cfg(feature = "serde")]
# {
use value_lang::Value;
let json = serde_json::to_string(&Value::int(-5)).unwrap();
assert_eq!(json, r#"{"Int":-5}"#);
# }
```
### Tests and benchmarks
- A `proptest` suite (`tests/roundtrip.rs`) asserts the invariants that make the representation trustworthy: pack/unpack is the identity for every kind, *every* `f64` bit pattern (NaNs included) classifies as a float, symbol ids round-trip, and the kinds are mutually exclusive.
- Criterion benchmarks (`benches/bench.rs`) cover construction, kind tests, payload reads, unpacking, and equality.
## Performance
Latest local Criterion means, Linux x86_64 (WSL2 Ubuntu), Rust stable, release build:
| 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 |
Numbers vary by CPU; run `cargo bench` on your target for a baseline.
## Breaking changes
**None** that affect real code — v0.1.0 was a scaffold with no public API. Two structural repairs landed on the way: the manifest `keywords` / `categories` were unquoted bare identifiers (the crate did not parse as valid TOML) and are now proper string arrays, and the `clippy.toml` MSRV was aligned with `Cargo.toml` at 1.85.
## Verification
Run on Windows x86_64 with testing on Linux (WSL2 Ubuntu), Rust stable 1.96.x and the 1.85 MSRV; the same commands run in the CI matrix across Linux, macOS, and Windows:
```bash
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo test --all-features
cargo build --no-default-features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo deny check
```
All green. Counts at this tag:
- Default features: 13 unit + 6 property + 9 doctests.
- `--all-features`: 16 unit + 6 property + 9 doctests.
## What's next
- **1.0.0 — API freeze.** Mark `docs/API.md` stable, record the SemVer promise, and confirm the full test and benchmark suite green on all three platforms.
## Installation
```toml
[dependencies]
value-lang = "0.2"
# With serde:
value-lang = { version = "0.2", features = ["serde"] }
# no_std:
value-lang = { version = "0.2", default-features = false }
```
MSRV: Rust 1.85.
## Documentation
- [README](https://github.com/jamesgober/value-lang/blob/main/README.md)
- [API Reference](https://github.com/jamesgober/value-lang/blob/main/docs/API.md)
- [CHANGELOG](https://github.com/jamesgober/value-lang/blob/main/CHANGELOG.md)
---
**Full diff:** [`v0.1.0...v0.2.0`](https://github.com/jamesgober/value-lang/compare/v0.1.0...v0.2.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/value-lang/blob/main/CHANGELOG.md#020---2026-07-01).