<h1 align="center">
<img width="99" alt="Rust logo" src="https://raw.githubusercontent.com/jamesgober/rust-collection/72baabd71f00e14aa9184efcb16fa3deddda3a0a/assets/rust-logo.svg">
<br>
<b>intern-lang</b>
<br>
<sub><sup>STRING & SYMBOL INTERNING</sup></sub>
</h1>
<div align="center">
<a href="https://crates.io/crates/intern-lang"><img alt="Crates.io" src="https://img.shields.io/crates/v/intern-lang"></a>
<a href="https://crates.io/crates/intern-lang"><img alt="Downloads" src="https://img.shields.io/crates/d/intern-lang?color=%230099ff"></a>
<a href="https://docs.rs/intern-lang"><img alt="docs.rs" src="https://img.shields.io/docsrs/intern-lang"></a>
<a href="https://github.com/jamesgober/intern-lang/actions"><img alt="CI" src="https://github.com/jamesgober/intern-lang/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://github.com/rust-lang/rfcs/blob/master/text/2495-min-rust-version.md"><img alt="MSRV" src="https://img.shields.io/badge/MSRV-1.85%2B-blue"></a>
</div>
<br>
<div align="left">
<p>
intern-lang turns repeated identifiers into small copyable symbols backed by a single contiguous store, so a compiler front-end compares names by integer equality instead of walking bytes. It is the deduplication layer a lexer and symbol table lean on: intern once, then pass cheap Copy handles everywhere a string name would otherwise travel.
</p>
<br>
<hr>
<p>
<strong>MSRV is 1.85+</strong> (Rust 2024 edition).
</p>
<blockquote>
<strong>Status: pre-1.0, in active development.</strong> The public API is being designed across the 0.x series and frozen at <code>1.0.0</code>. See <a href="./CHANGELOG.md"><code>CHANGELOG.md</code></a>.
</blockquote>
</div>
<hr>
<br>
## Installation
```toml
[dependencies]
intern-lang = "0.2"
```
<br>
## Quick start
```rust
use intern_lang::Interner;
let mut interner = Interner::new();
// Intern returns a small Copy handle; the same string always returns the same one.
let while_kw = interner.intern("while");
let for_kw = interner.intern("for");
assert_eq!(interner.intern("while"), while_kw);
// Name comparisons are now integer comparisons, not byte walks.
assert_ne!(while_kw, for_kw);
// Resolve borrows the original bytes straight out of the store.
assert_eq!(interner.resolve(while_kw), Some("while"));
```
Folding a token stream down to symbols, so later passes compare integers and a
name in an AST node costs four bytes instead of an owned `String`:
```rust
use intern_lang::{Interner, Symbol};
let mut interner = Interner::new();
let source = ["let", "x", "=", "x", "+", "x"];
// The three `x` occurrences collapsed to one symbol.
assert_eq!(tokens[1], tokens[3]);
assert_eq!(interner.len(), 4); // let, x, =, +
```
<br>
## Features
- **Four-byte handle.** A `Symbol` is a `NonZeroU32` newtype — `Copy`, integer
equality / ordering / hashing, and `Option<Symbol>` is four bytes too.
- **Bytes stored once.** Interned strings are appended end to end in a single
contiguous buffer; the dedup index stores symbol ids, not a second copy.
- **Allocation-free hits.** Interning a string already seen is a hash lookup with
no allocation and no copy.
- **Growth-stable symbols.** A symbol keeps resolving to the same string for the
interner's whole lifetime, including after the backing store reallocates.
- **`no_std`.** Relies only on `alloc`; the default `std` feature is additive. No
runtime dependencies beyond an optional `serde` (planned).
- **`#![forbid(unsafe_code)]`.** The contiguous store is implemented without any
`unsafe`.
<br>
## Performance
Resolution is a side-table index plus a slice — measured at roughly **1.1 ns per
`resolve`** (Criterion mean, Windows x86_64, Rust stable, on the development
machine). Interning an already-seen string is allocation-free: a hash over the
bytes, an open-addressing probe, and one byte comparison to confirm the hit. The
numbers below are a v0.x baseline, tracked over time rather than advertised as
final:
| `resolve` (id → `&str`) | ~1.1 ns |
| `intern` (repeat hit, no allocation) | ~0.23 µs |
| `intern` (new string, amortised growth) | ~0.62 µs |
Run them yourself with `cargo bench`.
<br>
## API overview
For the complete reference with examples, see [`docs/API.md`](./docs/API.md).
- [`Symbol`](./docs/API.md#symbol) — four-byte `Copy` handle to an interned string.
- [`Interner`](./docs/API.md#interner) — single-threaded interner: `intern`,
`get`, `resolve`, `len`, `with_capacity`.
<br>
## Status
`v0.2.0` ships the core interner and symbol. The thread-safe `ConcurrentInterner`,
optional `serde` for `Symbol`, and the defined symbol-space-exhaustion result land
across the remaining 0.x series per the <a href="./dev/ROADMAP.md"><code>ROADMAP</code></a>; the public surface freezes at <code>1.0.0</code>.
<hr>
<br>
## Contributing
See <a href="./dev/DIRECTIVES.md"><code>dev/DIRECTIVES.md</code></a> for 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.
<br>
<div id="license">
<h2>License</h2>
<p>Licensed under either of</p>
<ul>
<li><b>Apache License, Version 2.0</b> — <a href="./LICENSE-APACHE">LICENSE-APACHE</a></li>
<li><b>MIT License</b> — <a href="./LICENSE-MIT">LICENSE-MIT</a></li>
</ul>
<p>at your option.</p>
</div>
<div align="center">
<h2></h2>
<sup>COPYRIGHT <small>©</small> 2026 <strong>James Gober <me@jamesgober.com>.</strong></sup>
</div>