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