# varmap
A Rust library for **heterogeneous, typed key–value maps** where each key holds exactly one value of a known type at a time. Values are stored in a compact 16-byte tagged representation; strings, byte slices, and large integers use an internal arena so reads return borrowed references (`&str`, `&[u8]`) without per-lookup allocation.
Three map types share the same value encoding and getter API, but differ in how keys are represented:
| [`VarMap`](#varmap) | Precomputed `Key` (64-bit hash) | Keys are known at compile time (`var!` macro) |
| [`StrVarMap`](#strvarmap) | `&str` (FNV-1a hash at runtime) | Keys come from user input or config at runtime |
| [`EnumVarMap`](#enumvarmap) | Enum variant (`#[derive(EnumVarMap)]`) | Fixed, closed set of keys known at compile time |
## Usage model
VarMap is aimed at **write once, read many** workloads: you populate keys (configuration, request context, parsed attributes) and then read them repeatedly with cheap typed getters and borrowed `&str` / `&[u8]` results.
The internal arena is **append-only**. Calling `set` on an existing key updates the map entry, but any previous arena allocation for that key is **not** reclaimed. Each overwrite of an arena-backed value (strings and byte slices longer than 14 bytes, `i128` / `u128`, `Ipv6Addr`, custom types, etc.) adds new arena space while the old payload remains until the whole map is reset. Scalars and short strings (≤14 bytes) live entirely in the fixed 16-byte cell, so overwriting those does not grow the arena.
| Set each key once, then many reads | Ideal |
| Occasional updates to a few keys | Fine |
| Frequent updates to the same keys with large / arena-backed values | Poor — arena memory grows; prefer `clear()` and repopulate, or another map |
| Hot loop re-assigning every key every tick | Poor — use `clear()` between logical snapshots, or avoid varmap |
Use `clear()` when you need a fresh binding of all keys (for example between requests or benchmark iterations). That resets the arena and removes current entries while preserving already allocated capacity for future writes.
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
varmap = "0.1"
```
The proc-macros (`var!`, `#[derive(EnumVarMap)]`, `#[derive(VarMapValue)]`) are re-exported from the `varmap` crate.
## Supported value types
Built-in types (via `set` / `get` / typed getters):
- Integers: `i8` … `i64`, `u8` … `u64`, `i128`, `u128`
- Floats: `f32`, `f64`
- `bool`, `char`
- `&str`, `&[u8]` (stored in the arena; returned as borrows)
- `IpAddr`, `Ipv4Addr`, `Ipv6Addr`
Strings and byte slices up to **14 bytes** are stored inline in the map; larger payloads are arena-allocated.
Custom `Copy` types with alignment 1–16 can use `#[derive(VarMapValue)]` (see [Custom types](#custom-types)).
---
## StrVarMap
Use when variable names are ordinary strings at runtime (configuration keys, script variables, etc.). Keys are hashed with FNV-1a on each `set` / `get`.
```rust
use varmap::StrVarMap;
let mut map = StrVarMap::new();
map.set("port", 8080u16);
map.set("enabled", true);
map.set("host", "api.example.com");
assert_eq!(map.get_u16("port"), Some(8080));
assert_eq!(map.get_bool("enabled"), Some(true));
assert_eq!(map.get_str("host"), Some("api.example.com"));
assert_eq!(map.contains("port"), true);
// Generic get when the type is inferred or specified explicitly
let port: u16 = map.get("port").unwrap();
```
### Typed getters
All three maps provide the same convenience methods:
`get_bool`, `get_u8`, `get_u16`, `get_u32`, `get_u64`, `get_i8`, `get_i16`, `get_i32`, `get_i64`, `get_f32`, `get_f64`, `get_str`, `get_bytes`, `get_char`, `get_ip`, `get_ipv4`, `get_ipv6`
Or use the generic API:
```rust
map.get::<u32>("user.age");
map.get::<&str>("user.name");
```
### In-place updates
All three maps (`VarMap`, `StrVarMap`, and `EnumVarMap`) support in-place mutation through `update`.
This is useful for counters and custom `Copy` values that implement `VarMapValue::update` (the derive macro generates this automatically).
```rust
use varmap::{StrVarMap, VarMapValue};
#[derive(VarMapValue, Copy, Clone, Eq, PartialEq, Debug)]
struct Stats {
hits: u32,
misses: u32,
}
let mut map = StrVarMap::new();
map.set("count", 10u32);
map.set("stats", Stats { hits: 1, misses: 0 });
assert_eq!(map.get::<Stats>("stats").map(|s| s.hits), Some(2));
```
`update` returns `false` if the key is missing, the stored type does not match, or the type cannot be updated in place.
### Other operations
```rust
map.clear(); // drop all keys and reset the arena
```
---
## VarMap
Use when key names are **fixed at compile time**. The `var!` macro expands to a `Key` holding the FNV-1a hash of the string literal, so lookups skip string hashing and comparison.
```rust
use varmap::{VarMap, Key, var};
let mut map = VarMap::new();
// Compile-time keys (recommended)
map.set(var!("user.age"), 32u32);
map.set(var!("user.name"), "Alice");
assert_eq!(map.get_u32(var!("user.age")), Some(32));
assert_eq!(map.get_str(var!("user.name")), Some("Alice"));
map.set(Key::new(0x0123_4567_89AB_CDEF), 1u8);
```
`StrVarMap` is a thin wrapper around `VarMap` that hashes `&str` keys at runtime; `VarMap` + `var!` is the faster option when names are static.
### Limits
`VarMap` (and therefore `StrVarMap`) supports at most **65 536** distinct keys (index stored in the low 16 bits of the internal hash entry).
---
## EnumVarMap
Use when the key set is a **closed enum** known at compile time. Each variant maps to a fixed slot (one per enum discriminant), giving **O(1)** access with no hashing or binary search.
Derive `EnumVarMap` on your enum (requires `#[repr(u16)]`, `Copy`, and at most 65 536 variants):
```rust
use varmap::EnumVarMap;
#[derive(EnumVarMap, Copy, Clone, Debug)]
#[repr(u16)]
enum ConfigKey {
Port,
Enabled,
Host,
}
let mut map = EnumVarMap::<ConfigKey>::new();
map.set(ConfigKey::Port, 8080u16);
map.set(ConfigKey::Enabled, true);
map.set(ConfigKey::Host, "api.example.com");
assert_eq!(map.get_u16(ConfigKey::Port), Some(8080));
assert_eq!(map.get_bool(ConfigKey::Enabled), Some(true));
assert_eq!(map.get_str(ConfigKey::Host), Some("api.example.com"));
assert_eq!(map.contains(ConfigKey::Port), true);
```
`EnumVarMap` owns its own arena (unlike `StrVarMap`, which delegates to an inner `VarMap`). Memory for every enum variant slot is reserved up front, including variants that have not been set yet.
---
## Custom types
Types that are `Copy`, have alignment between 1 and 16 bytes, and contain only plain data can derive `VarMapValue`:
```rust
use varmap::{StrVarMap, VarMapValue};
#[derive(VarMapValue, Copy, Clone, Eq, PartialEq, Debug)]
struct Point {
x: i32,
y: i32,
}
let mut map = StrVarMap::new();
let p = Point { x: 1, y: 2 };
map.set("origin", p);
let back: &Point = map.get("origin").unwrap();
assert_eq!(*back, p);
```
`#[derive(VarMapValue)]` also generates in-place update support, so custom values can be mutated with `map.update::<T>(...)` on all three map types.
Generics, unions, and non-`Copy` types are not supported by the derive macro.
---
## Choosing a map
```
compile-time keys?
│
┌────────────┴────────────┐
yes no
│ │
fixed enum? StrVarMap
│
┌────────┴────────┐
yes no
│ │
EnumVarMap VarMap + var!
```
- **`EnumVarMap`** — fastest reads/writes when the key set is an enum; pays fixed storage for every variant slot.
- **`VarMap` + `var!`** — same value layer as `StrVarMap`, but no per-operation string hashing.
- **`StrVarMap`** — simplest API when keys are dynamic strings.
---
## Performance
Benchmarks live in the `banches` crate. They reflect the intended **read-heavy** pattern: each run performs **10 000** outer iterations over **256** string keys. **Update** tests call `clear()` at the start of every iteration, then write all keys once (so arena growth from repeated overwrites is not measured). **Read** tests populate the map once, then only read in the timed loop — matching write-once, read-many usage.
Results below are averaged over repeated runs; columns are **time**, **allocation during init**, and **allocation during the timed loop** (via a tracking allocator).
| **EnumVarMap** | Update – large strings | 23 ms | 4 112 B | 16 384 B |
| **VarMap** | Update – large strings | 74 ms | 0 B | 28 672 B |
| **StrVarMap** | Update – large strings | 90 ms | 0 B | 29 696 B |
| HashMap | Update – large strings | 175 ms | 0 B | 30 641 B |
| BTreeMap | Update – large strings | 258 ms | 0 B | 26 985 B |
| **EnumVarMap** | Update – small strings | 31 ms | 4 112 B | 0 B |
| **VarMap** | Update – small strings | 75 ms | 0 B | 12 288 B |
| **StrVarMap** | Update – small strings | 92 ms | 0 B | 12 288 B |
| HashMap | Update – small strings | 174 ms | 0 B | 22 026 B |
| BTreeMap | Update – small strings | 255 ms | 0 B | 18 370 B |
| **EnumVarMap** | Read – large strings | 16 ms | 20 496 B | 0 B |
| **VarMap** | Read – large strings | 59 ms | 28 672 B | 0 B |
| HashMap | Read – large strings | 58 ms | 30 641 B | 0 B |
| **StrVarMap** | Read – large strings | 85 ms | 28 672 B | 0 B |
| BTreeMap | Read – large strings | 93 ms | 26 985 B | 0 B |
| **EnumVarMap** | Read – small strings | 10 ms | 4 112 B | 0 B |
| **VarMap** | Read – small strings | 35 ms | 12 288 B | 0 B |
| HashMap | Read – small strings | 52 ms | 22 026 B | 0 B |
| **StrVarMap** | Read – small strings | 54 ms | 12 288 B | 0 B |
| BTreeMap | Read – small strings | 90 ms | 18 370 B | 0 B |
*Large* values are long descriptive strings (~50+ bytes); *small* values are short tokens (a few bytes), many of which use inline storage (≤14 bytes) instead of the arena.
### Takeaways
1. **Read benchmarks are the sweet spot** (populate once, read many): `EnumVarMap` leads by a wide margin (e.g. 10 ms vs 35–54 ms for small strings on read).
2. **`EnumVarMap` is fastest** overall: direct indexing avoids hash tables and, for `VarMap`/`StrVarMap`, sorted hash-vector lookup.
3. **`VarMap` beats `StrVarMap`** by roughly 15–20% because `var!` removes runtime FNV-1a over key names.
4. **All three varmap types beat `HashMap` and `BTreeMap`** on the measured workloads. Update numbers assume `clear()` per iteration; sustained in-place overwrites without `clear()` would allocate more because of the append-only arena.
5. **`EnumVarMap` trades memory for speed**: ~4 KiB upfront per 256-key enum (empty slots), but **zero** run-time allocation on small-string read/update tests once initialized.
6. **Inline small strings** (≤14 bytes) avoid arena use: `EnumVarMap` reports 0 bytes allocated during small-string update loops.
Reproduce locally from the workspace root:
```bash
cargo run -p banches -- RUN 10000 10
```
Use `cargo run -p banches -- LIST` to see individual benchmark names and filter with an optional fourth argument (e.g. `enumvarmap`).
---
## License
MIT — see repository and crate metadata for details.