structured_asn1/memory/map.rs
1//! Accounted storage and pre-admitted hashing for the existing hashbrown engine.
2
3use core::borrow::Borrow;
4use core::fmt;
5use core::hash::{BuildHasher, Hash};
6
7use hashbrown::hash_map::RawEntryMut;
8use hashbrown::{DefaultHashBuilder, HashMap, TryReserveError};
9
10use super::allocator::{self, TableAllocator};
11use super::{Memory, MemoryError, Work};
12
13#[cfg(test)]
14mod tests;
15
16/// A refused in-place filter, preserving the predicate's own cause.
17#[derive(Debug, PartialEq, Eq)]
18pub enum RetainError<E> {
19 /// The map could not admit its traversal or erase metadata.
20 Resource(MemoryError),
21 /// The caller's predicate refused further processing.
22 Predicate(E),
23}
24
25impl From<RetainError<MemoryError>> for MemoryError {
26 fn from(error: RetainError<MemoryError>) -> Self {
27 match error {
28 RetainError::Resource(reason) | RetainError::Predicate(reason) => reason,
29 }
30 }
31}
32
33/// A trusted key type's upper bound for hashing and equality work.
34///
35/// Implementations must cover all bytes examined by Hash and Eq. This is a
36/// contract for caller-provided code, not a value an untrusted document supplies.
37/// The bound must remain unchanged and successful while a key is stored, just
38/// as Hash and Eq must remain stable. Accounting retains this bound for rehashes.
39pub trait HashWork: Hash + Eq {
40 /// Work units for one hash/equality traversal, including constant overhead.
41 fn hash_work(&self) -> Result<u64, MemoryError>;
42}
43
44impl HashWork for usize {
45 fn hash_work(&self) -> Result<u64, MemoryError> {
46 Ok(1)
47 }
48}
49
50impl HashWork for u8 {
51 fn hash_work(&self) -> Result<u64, MemoryError> {
52 Ok(1)
53 }
54}
55
56impl HashWork for (usize, usize, usize) {
57 fn hash_work(&self) -> Result<u64, MemoryError> {
58 Ok(3)
59 }
60}
61
62impl HashWork for &[u8] {
63 fn hash_work(&self) -> Result<u64, MemoryError> {
64 Ok(self.len() as u64 + 1)
65 }
66}
67
68impl HashWork for str {
69 fn hash_work(&self) -> Result<u64, MemoryError> {
70 (self.len() as u64)
71 .checked_add(1)
72 .ok_or(MemoryError::WorkExceeded)
73 }
74}
75
76impl HashWork for crate::Oid<'_> {
77 fn hash_work(&self) -> Result<u64, MemoryError> {
78 Ok(self.as_bytes().len() as u64 + 1)
79 }
80}
81
82impl HashWork for (crate::Oid<'_>, crate::Oid<'_>) {
83 fn hash_work(&self) -> Result<u64, MemoryError> {
84 self.0
85 .hash_work()?
86 .checked_add(self.1.hash_work()?)
87 .ok_or(MemoryError::WorkExceeded)
88 }
89}
90
91/// A hash map whose backing allocation and operations share one resource domain.
92///
93/// Hashbrown supplies bucket/probing/SIMD machinery. Reservations include its
94/// control bytes and alignment, not merely the initialized entries. Hashing and
95/// actual collision comparisons are admitted before examining candidate keys.
96///
97/// The table's storage belongs to its account, not to the execution `W`, so
98/// [`OwnedMap::into_owned`] and [`OwnedMap::with_execution`] move a finished
99/// table between executions without copying or rehashing it.
100pub struct OwnedMap<K: HashWork, V, W: Work = Memory> {
101 values: HashMap<K, V, DefaultHashBuilder, TableAllocator>,
102 execution: W,
103 key_work: u64,
104 maximum_capacity: usize,
105}
106
107impl<K: HashWork, V> OwnedMap<K, V> {
108 /// Temporarily attaches the current operation's work ceilings.
109 ///
110 /// The execution must use this table's existing account. On mismatch the
111 /// original map is returned unchanged. On success the table, entries,
112 /// capacity, charge and stored key metadata move without copying,
113 /// rehashing, allocation or new work. Previously admitted work is neither
114 /// reset nor charged again. Later [`OwnedMap::try_insert`] growth is paid
115 /// by `execution`; reads keep using the execution passed to each call.
116 /// Call [`OwnedMap::into_owned`] to retain the table independently again.
117 ///
118 /// # Examples
119 /// ```
120 /// use std::sync::Arc;
121 /// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
122 /// let memory = Memory::new(Arc::new(MemoryAccount::new(4096, 4096)));
123 /// let mut map = memory.map();
124 /// map.try_insert(1usize, 10).unwrap();
125 /// let other = Memory::new(Arc::new(MemoryAccount::new(4096, 4096)));
126 /// let map = map.with_execution(&other).unwrap_err();
127 /// let parser = Parser::ber(memory.clone());
128 /// let reader = parser.read(&[], 0).unwrap();
129 /// let mut scoped = map.with_execution(&reader.execution()).unwrap();
130 /// scoped.try_insert(2, 20).unwrap();
131 /// let map = scoped.into_owned();
132 /// assert_eq!(map.get(&2, &memory).unwrap(), Some(&20));
133 /// ```
134 pub fn with_execution<W: Work>(self, execution: &W) -> Result<OwnedMap<K, V, W>, Self> {
135 if !self.memory().same_account(execution.memory()) {
136 return Err(self);
137 }
138 Ok(OwnedMap {
139 values: self.values,
140 execution: execution.clone(),
141 key_work: self.key_work,
142 maximum_capacity: self.maximum_capacity,
143 })
144 }
145}
146
147impl<K: HashWork + fmt::Debug, V: fmt::Debug, W: Work> fmt::Debug for OwnedMap<K, V, W> {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 self.values.fmt(f)
150 }
151}
152
153impl<K: HashWork, V, W: Work> OwnedMap<K, V, W> {
154 pub(super) fn new(execution: W) -> Self {
155 Self {
156 values: HashMap::with_hasher_in(
157 DefaultHashBuilder::default(),
158 TableAllocator::new(execution.memory().clone()),
159 ),
160 execution,
161 key_work: 0,
162 maximum_capacity: 0,
163 }
164 }
165
166 /// Finishes bounded construction as an independently owned table.
167 ///
168 /// Transfers the existing table and its original account without copying
169 /// entries, rehashing, reallocating, changing capacity or reserving again.
170 /// Subsequent growth uses that aggregate account; the parser-local ceiling
171 /// governed construction and is no longer attached. Borrows in `K` and `V`
172 /// retain their own lifetimes.
173 ///
174 /// # Examples
175 /// ```
176 /// use std::sync::Arc;
177 /// use structured_asn1::{Parser, memory::{Memory, MemoryAccount, Work}};
178 /// let memory = Memory::new(Arc::new(MemoryAccount::new(4096, 4096)));
179 /// let result;
180 /// {
181 /// let parser = Parser::ber(memory.clone());
182 /// let reader = parser.read(&[], 0)?;
183 /// let mut scratch = reader.execution().map();
184 /// scratch.try_insert(7usize, "seven").unwrap();
185 /// result = scratch.into_owned();
186 /// }
187 /// assert_eq!(result.get(&7, &memory).unwrap(), Some(&"seven"));
188 /// drop(result);
189 /// assert_eq!(memory.used(), 0);
190 /// # Ok::<(), structured_asn1::Error>(())
191 /// ```
192 #[must_use]
193 pub fn into_owned(self) -> OwnedMap<K, V> {
194 // One shared-owner increment per finished table: the account handle
195 // the table already holds cannot also serve as the map's execution
196 // while hashbrown borrows the table mutably during insertion.
197 let execution = self.values.allocator().memory().clone();
198 OwnedMap {
199 values: self.values,
200 execution,
201 key_work: self.key_work,
202 maximum_capacity: self.maximum_capacity,
203 }
204 }
205
206 /// The shared account retained until the table is deallocated.
207 #[must_use]
208 pub fn memory(&self) -> &Memory {
209 self.values.allocator().memory()
210 }
211
212 /// Number of initialized entries.
213 #[must_use]
214 pub fn len(&self) -> usize {
215 self.values.len()
216 }
217
218 /// Whether there are no entries.
219 #[must_use]
220 pub fn is_empty(&self) -> bool {
221 self.values.is_empty()
222 }
223
224 /// Borrowed entries after admitting a full control-bucket traversal in
225 /// the current execution domain, independently of retained storage.
226 /// Sparse holes count too. Partial consumption retains the conservative
227 /// prepayment; the opaque iterator exposes no cloning/restart operation.
228 ///
229 /// # Examples
230 /// ```
231 /// # extern crate alloc;
232 /// # use alloc::sync::Arc;
233 /// # use structured_asn1::memory::{Memory, MemoryAccount, MemoryError};
234 /// # let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
235 /// let mut map = memory.map();
236 /// map.try_insert(1usize, 42)?;
237 /// let mut entries = map.try_iter(&memory)?;
238 /// assert_eq!(entries.next(), Some((&1, &42)));
239 /// # Ok::<(), MemoryError>(())
240 /// ```
241 pub fn try_iter<'a, E: Work>(
242 &'a self,
243 execution: &E,
244 ) -> Result<impl Iterator<Item = (&'a K, &'a V)> + 'a + use<'a, K, V, W, E>, MemoryError> {
245 execution.charge_work(self.buckets()?)?;
246 Ok(self.values.iter())
247 }
248
249 /// Retains entries selected by a fallible predicate, without allocating.
250 ///
251 /// The supplied current execution admits the entire control-bucket scan,
252 /// including sparse holes, before the first predicate call. Every erase
253 /// separately admits its metadata work before removal. The predicate must
254 /// admit its own data-dependent work; arbitrary key/value destructors are
255 /// likewise caller-owned code. No key hashing or equality is performed.
256 ///
257 /// Entries are visited in unspecified order. On the first refusal no more
258 /// predicates or key-work metadata callbacks run; the engine finishes its
259 /// already admitted scan while retaining all remaining entries. Earlier
260 /// removals and predicate value edits, including an edit made by a failing
261 /// predicate, remain. Refusal never reports a successful partial filter.
262 ///
263 /// The original allocation, capacity charge and storage account remain
264 /// unchanged. Future growth still uses the map's original execution.
265 /// Filtering under another execution does not rebind that allocator.
266 ///
267 /// # Examples
268 /// ```
269 /// # extern crate alloc;
270 /// # use alloc::sync::Arc;
271 /// use structured_asn1::memory::{Memory, MemoryAccount};
272 /// let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
273 /// let mut map = memory.map();
274 /// map.try_insert(1usize, 10).unwrap();
275 /// map.try_insert(2usize, 20).unwrap();
276 /// map.try_retain(&memory, |key, value| {
277 /// *value += 1;
278 /// Ok::<_, core::convert::Infallible>(*key == 2)
279 /// }).unwrap();
280 /// assert_eq!(map.len(), 1);
281 /// assert_eq!(map.get(&2, &memory).unwrap(), Some(&21));
282 /// ```
283 pub fn try_retain<E>(
284 &mut self,
285 execution: &impl Work,
286 mut predicate: impl FnMut(&K, &mut V) -> Result<bool, E>,
287 ) -> Result<(), RetainError<E>> {
288 let buckets = self.buckets().map_err(RetainError::Resource)?;
289 execution
290 .charge_work(buckets)
291 .map_err(RetainError::Resource)?;
292 let mut failure = None;
293 let key_work = &mut self.key_work;
294 self.values.retain(|key, value| {
295 if failure.is_some() {
296 return true;
297 }
298 match predicate(key, value) {
299 Ok(true) => true,
300 Err(reason) => {
301 failure = Some(RetainError::Predicate(reason));
302 true
303 }
304 Ok(false) => {
305 // Pinned hashbrown 0.17.1 RawTableInner::erase loads the
306 // adjacent two control groups, then drops the pair in place.
307 // It neither hashes keys nor moves entries to another table.
308 if let Err(reason) = execution.charge_work(2) {
309 failure = Some(RetainError::Resource(reason));
310 return true;
311 }
312 let removed = match key.hash_work() {
313 Ok(work) => work,
314 Err(reason) => {
315 failure = Some(RetainError::Resource(reason));
316 return true;
317 }
318 };
319 // HashWork's stored bound is stable by contract. Update
320 // before hashbrown erases and invokes a possibly panicking Drop.
321 debug_assert!(*key_work >= removed);
322 *key_work -= removed;
323 false
324 }
325 }
326 });
327 match failure {
328 Some(reason) => Err(reason),
329 None => Ok(()),
330 }
331 }
332
333 fn buckets(&self) -> Result<u64, MemoryError> {
334 // Pinned hashbrown 0.17.1 raw::bucket_mask_to_capacity: tables with
335 // four/eight buckets keep one empty slot; larger tables use 7/8 of
336 // the buckets. Maximum capacity records each table immediately after
337 // reserve, before tombstones can reduce the public capacity value.
338 match self.maximum_capacity {
339 0 => Ok(0),
340 capacity @ 1..=7 => Ok(capacity as u64 + 1),
341 capacity => (capacity as u64 / 7)
342 .checked_mul(8)
343 .ok_or(MemoryError::WorkExceeded),
344 }
345 }
346
347 fn control_work(&self) -> Result<u64, MemoryError> {
348 let buckets = self.buckets()?;
349 // The pinned engine's narrowest control group is four bytes. Wider
350 // scalar/SIMD groups perform the same fixed group operations. One unit
351 // admits one group visit; triangular probing visits each group once.
352 Ok(buckets / 4)
353 }
354
355 fn rehash_work(&self) -> Result<u64, MemoryError> {
356 // reserve(1) can double the current buckets or rehash in place. Every
357 // retained key is hashed once; each slot search may visit all groups.
358 // In-place rehash additionally scans every bucket and swaps entries.
359 let buckets = self.buckets()?;
360 let groups = buckets.checked_mul(2).ok_or(MemoryError::WorkExceeded)? / 4;
361 let moves = (core::mem::size_of::<(K, V)>() as u64)
362 .checked_mul(2)
363 .ok_or(MemoryError::WorkExceeded)?;
364 groups
365 .checked_add(moves)
366 .and_then(|per_entry| per_entry.checked_mul(self.values.len() as u64))
367 .and_then(|work| work.checked_add(self.key_work))
368 .and_then(|work| {
369 buckets
370 .checked_mul(2)
371 .and_then(|scan| work.checked_add(scan))
372 })
373 .ok_or(MemoryError::WorkExceeded)
374 }
375
376 fn hash_key<Q: HashWork + ?Sized, E: Work>(
377 &self,
378 key: &Q,
379 execution: &E,
380 ) -> Result<u64, MemoryError> {
381 let work = key
382 .hash_work()?
383 .checked_add(self.control_work()?)
384 .ok_or(MemoryError::WorkExceeded)?;
385 execution.charge_work(work)?;
386 Ok(self.values.hasher().hash_one(key))
387 }
388
389 /// Looks up a key after admitting its complete possible probe work.
390 /// The current execution domain pays independently of stored capacity.
391 ///
392 /// # Examples
393 /// ```
394 /// # extern crate alloc;
395 /// # use alloc::sync::Arc;
396 /// # use structured_asn1::memory::{Memory, MemoryAccount, MemoryError};
397 /// # let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
398 /// # let mut map = memory.map();
399 /// # map.try_insert(1usize, 42)?;
400 /// assert_eq!(map.get(&1, &memory)?, Some(&42));
401 /// assert_eq!(map.get(&2, &memory)?, None);
402 /// # Ok::<(), MemoryError>(())
403 /// ```
404 pub fn get(&self, key: &K, execution: &impl Work) -> Result<Option<&V>, MemoryError> {
405 self.get_key_value_borrowed(key, execution)
406 .map(|entry| entry.map(|(_, value)| value))
407 }
408
409 /// Finds the canonical stored key and value using an allocation-free query.
410 ///
411 /// Owned and borrowed forms must have identical Hash/Eq semantics, as
412 /// required by [`Borrow`]. Current work admits query hashing, table probes
413 /// and every candidate comparison before executing them. The stored key's
414 /// [`HashWork`] bound must also cover its `Borrow<Q>` operation, which must
415 /// not allocate. A refusal returns `Err`, never successful absence or a
416 /// synthetic match. No table/account is rebound and no query key is copied.
417 ///
418 /// Returning the canonical key lets an interner clone its existing shared
419 /// owner without a second lookup or a temporary owned query allocation.
420 ///
421 /// ```
422 /// use std::sync::Arc;
423 /// use structured_asn1::memory::{Memory, MemoryAccount};
424 /// let storage = Memory::new(Arc::new(MemoryAccount::new(4096, 4096)));
425 /// let mut map = storage.map();
426 /// map.try_insert(7usize, "retained")?;
427 /// let query = Memory::new(Arc::new(MemoryAccount::new(0, 1024)));
428 /// assert_eq!(map.get_key_value_borrowed(&7, &query)?, Some((&7, &"retained")));
429 /// # Ok::<(), structured_asn1::memory::MemoryError>(())
430 /// ```
431 pub fn get_key_value_borrowed<Q: HashWork + ?Sized>(
432 &self,
433 key: &Q,
434 execution: &impl Work,
435 ) -> Result<Option<(&K, &V)>, MemoryError>
436 where
437 K: Borrow<Q>,
438 {
439 let hash = self.hash_key(key, execution)?;
440 let mut failure = None;
441 let entry = self.values.raw_entry().from_hash(hash, |candidate| {
442 equivalent(execution, key, candidate, &mut failure)
443 });
444 if let Some(reason) = failure {
445 return Err(reason);
446 }
447 Ok(entry)
448 }
449
450 /// Looks up an entry for mutation without exposing table growth.
451 /// Hashing, probing and equality use the supplied execution domain.
452 ///
453 /// # Examples
454 /// ```
455 /// # extern crate alloc;
456 /// # use alloc::sync::Arc;
457 /// # use structured_asn1::memory::{Memory, MemoryAccount, MemoryError};
458 /// # let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
459 /// # let mut map = memory.map();
460 /// # map.try_insert(1usize, 42)?;
461 /// if let Some(value) = map.get_mut(&1, &memory)? { *value = 7; }
462 /// assert_eq!(map.get(&1, &memory)?, Some(&7));
463 /// # Ok::<(), MemoryError>(())
464 /// ```
465 pub fn get_mut(
466 &mut self,
467 key: &K,
468 execution: &impl Work,
469 ) -> Result<Option<&mut V>, MemoryError> {
470 self.get_mut_borrowed(key, execution)
471 }
472
473 /// Finds a mutable value using an allocation-free borrowed query.
474 ///
475 /// Uses the same Hash/Eq/`Borrow` and [`HashWork`] requirements as
476 /// [`Self::get_key_value_borrowed`]. Current work admits hashing, probes and
477 /// each candidate comparison before exposing a mutable value. Refusal is
478 /// `Err`, never absence or a synthetic match. No owned query, second lookup
479 /// or table/account rebinding is performed.
480 pub fn get_mut_borrowed<Q: HashWork + ?Sized>(
481 &mut self,
482 key: &Q,
483 execution: &impl Work,
484 ) -> Result<Option<&mut V>, MemoryError>
485 where
486 K: Borrow<Q>,
487 {
488 let hash = self.hash_key(key, execution)?;
489 let mut failure = None;
490 let entry = self.values.raw_entry_mut().from_hash(hash, |candidate| {
491 equivalent(execution, key, candidate, &mut failure)
492 });
493 if let Some(reason) = failure {
494 return Err(reason);
495 }
496 Ok(match entry {
497 RawEntryMut::Occupied(entry) => Some(entry.into_mut()),
498 RawEntryMut::Vacant(_) => None,
499 })
500 }
501
502 /// Inserts a value fallibly, or replaces the value for an existing key.
503 /// This storage mutation runs in the map's attached execution, including
504 /// hashing, rehashing, allocation and copying work. Table growth admits
505 /// its storage and work before the allocator backend, in the same order
506 /// as [`super::OwnedVec`]. To mutate under another same-account execution,
507 /// attach it with [`OwnedMap::with_execution`].
508 pub fn try_insert(&mut self, key: K, value: V) -> Result<Option<V>, MemoryError> {
509 let hash = self.hash_key(&key, &self.execution)?;
510 let mut failure = None;
511 let entry = self.values.raw_entry_mut().from_hash(hash, |candidate| {
512 equivalent(&self.execution, &key, candidate, &mut failure)
513 });
514 if let Some(reason) = failure {
515 return Err(reason);
516 }
517 if let RawEntryMut::Occupied(entry) = entry {
518 let moves = (core::mem::size_of::<V>() as u64)
519 .checked_mul(2)
520 .ok_or(MemoryError::WorkExceeded)?;
521 self.execution.charge_work(moves)?;
522 return Ok(Some(core::mem::replace(entry.into_mut(), value)));
523 }
524 let key_work = self
525 .key_work
526 .checked_add(key.hash_work()?)
527 .ok_or(MemoryError::WorkExceeded)?;
528 if self.values.len() == self.values.capacity() {
529 // Include collision-dependent slot searches, not just key hashes.
530 self.execution.charge_work(self.rehash_work()?)?;
531 self.values.allocator().clear_failure();
532 allocator::scoped(
533 &mut self.values,
534 HashMap::allocator,
535 &self.execution,
536 |values| values.try_reserve(1),
537 )
538 .map_err(|error| match error {
539 TryReserveError::CapacityOverflow => MemoryError::CapacityOverflow,
540 TryReserveError::AllocError { .. } => self.values.allocator().failure(),
541 })?;
542 self.maximum_capacity = self.maximum_capacity.max(self.values.capacity());
543 }
544 // Reuse the already computed hash after reserve. No key equality is
545 // needed: the first lookup established absence and resize preserves keys.
546 // raw_entry searches for a match; insert_hashed_nocheck subsequently
547 // searches for an insertion slot. Admit both control traversals.
548 self.execution.charge_work(
549 self.control_work()?
550 .checked_mul(2)
551 .and_then(|work| work.checked_add(core::mem::size_of::<(K, V)>() as u64))
552 .ok_or(MemoryError::WorkExceeded)?,
553 )?;
554 let RawEntryMut::Vacant(entry) = self.values.raw_entry_mut().from_hash(hash, |_| false)
555 else {
556 unreachable!("a predicate that always refuses cannot find an entry");
557 };
558 entry.insert_hashed_nocheck(hash, key, value);
559 self.key_work = key_work;
560 Ok(None)
561 }
562
563 /// Removes an entry, retaining table capacity and its storage charge.
564 /// The supplied execution domain pays for lookup and removal before mutation.
565 ///
566 /// # Examples
567 /// ```
568 /// # extern crate alloc;
569 /// # use alloc::sync::Arc;
570 /// # use structured_asn1::memory::{Memory, MemoryAccount, MemoryError};
571 /// # let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
572 /// # let mut map = memory.map();
573 /// # map.try_insert(1usize, 42)?;
574 /// let capacity_charge = memory.used();
575 /// assert_eq!(map.remove(&1, &memory)?, Some(42));
576 /// assert_eq!(memory.used(), capacity_charge);
577 /// # Ok::<(), MemoryError>(())
578 /// ```
579 pub fn remove(&mut self, key: &K, execution: &impl Work) -> Result<Option<V>, MemoryError> {
580 self.remove_borrowed(key, execution)
581 }
582
583 /// Removes a value using an allocation-free borrowed query.
584 ///
585 /// Uses the same Hash/Eq/`Borrow` and [`HashWork`] requirements as
586 /// [`Self::get_key_value_borrowed`]. Current work admits lookup and erase
587 /// metadata/value movement before mutation. Refusal leaves the entry intact.
588 /// The original table capacity and allocator remain retained; the removed
589 /// key is dropped and the value transfers its independent ownership.
590 /// Arbitrary key/value destructor work remains caller-owned.
591 pub fn remove_borrowed<Q: HashWork + ?Sized>(
592 &mut self,
593 key: &Q,
594 execution: &impl Work,
595 ) -> Result<Option<V>, MemoryError>
596 where
597 K: Borrow<Q>,
598 {
599 let hash = self.hash_key(key, execution)?;
600 let mut failure = None;
601 let entry = self.values.raw_entry_mut().from_hash(hash, |candidate| {
602 equivalent(execution, key, candidate, &mut failure)
603 });
604 if let Some(reason) = failure {
605 return Err(reason);
606 }
607 let entry = match entry {
608 RawEntryMut::Occupied(entry) => {
609 // Erase examines the adjacent control groups; returning the
610 // pair moves its storage. Admit both before changing the table.
611 let work = (core::mem::size_of::<(K, V)>() as u64)
612 .checked_mul(2)
613 .and_then(|work| work.checked_add(2))
614 .ok_or(MemoryError::WorkExceeded)?;
615 let removed_work = entry.key().hash_work()?;
616 execution.charge_work(work)?;
617 let (removed_key, value) = entry.remove_entry();
618 self.key_work -= removed_work;
619 // Settle metadata before caller-owned Drop can unwind.
620 drop(removed_key);
621 Some(value)
622 }
623 RawEntryMut::Vacant(_) => None,
624 };
625 Ok(entry)
626 }
627}
628
629/// A synthetic match stops hashbrown immediately on exhaustion. The caller
630/// checks failure before exposing or mutating the matched entry, so no false
631/// equality escapes and no expensive comparison runs after a refusal.
632fn equivalent<K: HashWork + Borrow<Q>, Q: HashWork + ?Sized, W: Work>(
633 memory: &W,
634 query: &Q,
635 candidate: &K,
636 failure: &mut Option<MemoryError>,
637) -> bool {
638 let cost = query.hash_work().and_then(|query| {
639 candidate.hash_work().and_then(|candidate| {
640 query
641 .checked_add(candidate)
642 .ok_or(MemoryError::WorkExceeded)
643 })
644 });
645 let admitted = cost.and_then(|cost| memory.charge_work(cost));
646 if let Err(reason) = admitted {
647 *failure = Some(reason);
648 return true;
649 }
650 query == candidate.borrow()
651}