std::collections::hash_map
HashMap
Deterministic open-addressed hash map with linear probing.
Overview
HashMap<K, V> stores keys and values in parallel contiguous buffers.
Lookup uses:
- deterministic hashing via
std::hash::Hasher - open addressing
- linear probing
- tombstones for removed entries
The map grows when the configured load factor would be exceeded. Capacity is always managed in buckets, not bytes.
Key Requirements
Keys must satisfy Hash & Eq.
In practice that means user-defined key types usually provide:
@implements(Hash, Eq)on the record- a
hash(&self, hasher: &mut Hasher): voidmethod - an
equals(&self, other: &K): booleanmethod
Bucket States
state = 0 -> empty
state = 1 -> occupied
state = 2 -> tombstone
Empty buckets stop probing. Tombstones preserve probe chains after remove.
Memory Layout
HashMap<K, V>
┌────────────────────────────────────────────────────────────┐
│ states: [E|O|T|O|...] │
│ hashes: [0|h|0|h|...] │
│ keys: [ |K| |K|...] │
│ values: [ |V| |V|...] │
│ length: active occupied buckets │
│ tombstones: removed buckets still participating in probe │
│ capacity: total bucket count │
└────────────────────────────────────────────────────────────┘
keys and values are manual-storage buffers. Only buckets marked as
occupied contain initialized logical elements.
Probe Sequence
A lookup starts at hash % capacity and advances one slot at a time until:
- it finds an empty bucket (
not present) - it finds a matching occupied bucket (
present) - it has examined every bucket (
full-table miss)
capacity = 8
start = hash % 8 = 3
index: 0 1 2 3 4 5 6 7
state: E T O O T O E E
^
start
probe order: 3 -> 4 -> 5 -> 6
- slot 3: occupied, different key
- slot 4: tombstone, continue
- slot 5: occupied, different key
- slot 6: empty, stop -> key does not exist
Insert / Replace Rules
Insert first reserves enough capacity, then probes exactly like lookup.
During probing it tracks:
- the first tombstone encountered
- whether an existing matching key was found
That gives three possible outcomes:
- matching key found -> replace value
- no key found, tombstone seen -> reuse first tombstone
- no key found, no tombstone seen -> use first empty bucket
Drop Behavior
HashMap owns both the key and the value stored in an occupied bucket.
That means:
- replacing an entry drops the previous key and returns the previous value
- removing an entry drops the key and returns the value
clear()drops every occupied key/value pairdrop()callsclear()and then frees the backing buffers
Tombstones do not hold logical values anymore; they only preserve probing structure until the next rehash.
Complexity
Average-case complexity:
insert->O(1)get->O(1)remove->O(1)reserve->O(n)when rehashing occurs
Worst-case probe chains are linear in the bucket count.
Example
import Eq from "std::collections";
import Hash from "std::collections";
import HashMap from "std::collections";
import Hasher from "std::hash";
@implements(Hash, Eq)
record Key {
id: i32;
hash(&self, hasher: &mut Hasher): void {
let mut state: &mut Hasher = hasher;
state.writeI32(self.id);
}
equals(&self, other: &Key): boolean {
return self.id == other.id;
}
}
function main(): i32 {
let mut map: HashMap<Key, i32> = HashMap::new<Key, i32>();
map.insert(Key { id: 1 }, 10);
map.insert(Key { id: 2 }, 20);
let lookup: Key = Key { id: 2 };
let value: i32 = match (map.get(&lookup)) {
Option::SOME(found) -> *found,
Option::NONE -> -1,
};
return value;
}
Records
record HashMap<K, V>Deterministic open-addressed map from K to V.
This container owns both keys and values. Replacing or removing an entry drops the previous key/value exactly once.
states: *mut u8hashes: *mut u64keys: *mut Kvalues: *mut Vlength(&self): u64Returns the number of occupied entries currently stored.
capacity(&self): u64Returns the number of buckets currently allocated.
tombstones: u64static init(capacity: u64): HashMap<K, V>Compatibility alias for HashMap::new(capacity).
static init(capacity: u64): HashMap<K, V>Compatibility alias for HashMap::new(capacity).
static new(capacity: u64): HashMap<K, V>Creates an empty map with enough buckets for at least capacity
logical entries at the current load-factor policy.
Example
import HashMap from "std::collections";
let map: HashMap<i32, i32> = HashMap::new<i32, i32>(64);
static new(capacity: u64): HashMap<K, V>Creates an empty map with enough buckets for at least capacity
logical entries at the current load-factor policy.
Example
import HashMap from "std::collections";
let map: HashMap<i32, i32> = HashMap::new<i32, i32>(64);
capacity(&self): u64Returns the number of buckets currently allocated.
clear(&mut self): voidDrops all occupied entries but keeps the bucket allocation for reuse.
Example
import HashMap from "std::collections";
let mut map: HashMap<i32, i32> = HashMap::new<i32, i32>();
map.insert(1, 10);
map.clear();
contains(&self, key: &K): booleanReturns true if key is present in the map.
This probes directly without materializing an intermediate Option<&V>.
Example
import HashMap from "std::collections";
let mut map: HashMap<i32, i32> = HashMap::new<i32, i32>();
map.insert(1, 10);
let found: boolean = map.contains(&1);
containsKey(&self, key: &K): booleanCompatibility alias for contains.
drop(&mut self): voidDrops all entries and releases all backing storage.
entryKey(&self, cursor: u64): &KReturns the key for a cursor previously returned by the entry cursor API.
entryValue(&self, cursor: u64): &VReturns the value for a cursor previously returned by the entry cursor API.
firstEntryCursor(&self): Option<u64>Returns the first occupied entry cursor in deterministic bucket order.
Cursors are opaque bucket positions that remain valid until the map is
mutated. Callers can advance them with nextEntryCursor and read the
pointed entry via entryKey / entryValue.
freeStorage(&mut self): voidReleases raw storage buffers and resets logical counters.
get(&self, key: &K): Option<&V>Returns an immutable reference to the value for key, if present.
Example
import HashMap from "std::collections";
let mut map: HashMap<i32, i32> = HashMap::new<i32, i32>();
map.insert(7, 99);
let value: Option<&i32> = map.get(&7);
getMut(&mut self, key: &K): Option<&mut V>Returns a mutable reference to the value for key, if present.
Example
import HashMap from "std::collections";
let mut map: HashMap<i32, i32> = HashMap::new<i32, i32>();
map.insert(7, 99);
let value: Option<&mut i32> = map.getMut(&7);
insert(&mut self, key: K, value: V): Option<V>Inserts or replaces the value for key.
Returns:
Option::NONEwhen the key was not presentOption::SOME(previous)when an existing value was replaced
Example
import HashMap from "std::collections";
let mut map: HashMap<i32, i32> = HashMap::new<i32, i32>();
let inserted: Option<i32> = map.insert(1, 10); // NONE
let replaced: Option<i32> = map.insert(1, 20); // SOME(10)
isEmpty(&self): booleanReturns true when the map contains no occupied entries.
length(&self): u64Returns the number of occupied entries currently stored.
nextEntryCursor(&self, cursor: u64): Option<u64>Returns the next occupied entry cursor after cursor, if any.
rehash(&mut self, newCapacity: u64): voidRebuilds the table into a new bucket array.
remove(&mut self, key: &K): Option<V>Removes key from the map and returns its previous value, if present.
Removal leaves a tombstone bucket behind so probe chains remain valid.
Example
import HashMap from "std::collections";
let mut map: HashMap<i32, i32> = HashMap::new<i32, i32>();
map.insert(1, 10);
let removed: Option<i32> = map.remove(&1);
reserve(&mut self, additional: u64): voidEnsures enough buckets exist to insert additional more entries without
violating the current load-factor policy.
Example
import HashMap from "std::collections";
let mut map: HashMap<i32, i32> = HashMap::new<i32, i32>();
map.reserve(32);
Traits
trait HashHashing contract required by HashMap and HashSet keys.
Implementations must write the same hash bytes for values that compare equal.
The trait is local to collections so key types can implement it without
importing the broader std::hash::Hash surface directly.
hash(&self): voidFeeds a stable representation of self into the provided hasher.
Functions
function __hashMapStorageBytes<T>(count: u64): u64Computes how many bytes must be reserved to store count elements of T.
Zero-sized element types still reserve one byte so pointer storage remains non-null and addressable by the implementation.
Constants
const EMPTY_STATE: u8const INITIAL_BUCKET_CAPACITY: u64const MAX_LOAD_PERCENT: u64const OCCUPIED_STATE: u8const TOMBSTONE_STATE: u8