value-lang 1.0.0

Runtime value representation (tagged unions / NaN-boxing).
Documentation
# 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:

| 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 |

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).