Skip to main content

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}