# intern-lang v1.0.0 — API freeze
**Stable.** v1.0.0 freezes the public surface under Semantic Versioning until 2.0.
There are no code changes from `0.4.0` — this release marks the contract: the
interner, the symbol, the concurrent variant, the read seam, and the exhaustion
error are now stable, and no breaking change will be made without a major version
bump.
## What is intern-lang?
A string interner. It maps each distinct string to a small, copyable `Symbol`,
stores the bytes once in a contiguous backing store, and hands back integer
handles so that comparing two names is an integer comparison rather than a byte
walk. It is the deduplication layer beneath the lexer and the symbol table:
intern an identifier once, then pass cheap `Copy` handles everywhere a string name
would otherwise travel. It owns interning and nothing else.
## The 1.0 surface
The whole public API, frozen as of this release:
- **`Symbol`** — a four-byte `Copy` handle over a `NonZeroU32`. Integer equality,
ordering, and hashing; `Option<Symbol>` is four bytes too. `as_u32` /
`from_u32`, and transparent `serde` behind the `serde` feature.
- **`Interner`** — the single-threaded interner: `new`, `with_capacity`, `intern`,
`try_intern`, `get`, `resolve`, `resolve_with`, `len`, `is_empty`.
- **`ConcurrentInterner`** — a thread-safe interner sharing one symbol space
(requires `std`): the same surface through `&self`, plus an owned `resolve`.
- **`Lookup`** — the read-side trait both interners implement, for generic code.
- **`InternError`** — the `#[non_exhaustive]` exhaustion error.
The design held from the start: bytes live once in a contiguous buffer, symbols
are stable indices into a side table, deduplication runs through an
open-addressing index that stores ids rather than a second copy of the bytes, and
the whole crate is `#![forbid(unsafe_code)]` and `no_std` (the single-threaded
interner needs only `alloc`).
## Guarantees, now under SemVer
The invariants are part of the contract and are property-tested against a
`HashMap` reference interner:
- **Dedup** — the same string always returns the same symbol.
- **Distinctness** — distinct strings never share a symbol.
- **Round-trip** — `resolve(intern(s))` is `Some(s)` for every accepted `s`.
- **Stability** — a symbol keeps resolving to the same string for its interner's
whole lifetime, including after the backing store grows.
- **Concurrency** — many threads interning at once never mint a duplicate symbol
for the same string.
- **Exhaustion** — reported through `try_intern`, never silently wrapped.
`InternError` stays `#[non_exhaustive]`, and SemVer's additive rule still permits
new methods, feature flags, and trait impls later — so the freeze locks the
existing surface without precluding growth.
## Performance
A v0.x baseline carried into 1.0, measured with Criterion on the development
machine (Windows x86_64, Rust stable):
| `resolve` (id → `&str`) | ~1.0 ns |
| `intern` (repeat hit, allocation-free) | ~0.23 µs / 1024-string batch element |
| `intern` (new string, amortised growth) | ~0.60 µs |
The concurrent warm read path scales across threads: at eight threads the
`ConcurrentInterner` sustains roughly 4× the single-thread intern throughput,
since hits are served under a shared read lock.
## Breaking changes
**None.** v1.0.0 is identical in behaviour to v0.4.0.
## Verification
The full suite was run green on **Linux (WSL2 Ubuntu)** and **Windows x86_64**
against Rust stable 1.95.x and MSRV 1.85, and on **macOS** through the CI matrix
(Linux / macOS / Windows × stable / 1.85):
```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 +1.85 test --all-features
cargo bench --no-run
cargo audit
cargo deny check
```
All green. Counts at this tag:
- Default features: 70 tests (27 unit + 17 integration/property + 26 doctests).
- `--all-features`: 75 tests (adds the 5-test `serde` round-trip suite).
- `--no-default-features`: the single-threaded interner builds clean as `no_std`.
The benchmark suite (`cargo bench`) runs all four core groups and the three
concurrent thread-count variants green.
## What's next
1.0 is the stability line. Future work is bug fixes, performance, documentation,
and additive API only — anything breaking waits for a 2.0.
## Installation
```toml
[dependencies]
intern-lang = "1"
# optional: serde support for `Symbol`
intern-lang = { version = "1", features = ["serde"] }
```
MSRV: Rust 1.85.
## Documentation
- [README](https://github.com/jamesgober/intern-lang/blob/main/README.md)
- [API Reference](https://github.com/jamesgober/intern-lang/blob/main/docs/API.md)
- [CHANGELOG](https://github.com/jamesgober/intern-lang/blob/main/CHANGELOG.md)
---
**Full diff:** [`v0.4.0...v1.0.0`](https://github.com/jamesgober/intern-lang/compare/v0.4.0...v1.0.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/intern-lang/blob/main/CHANGELOG.md#100---2026-06-20).