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