Skip to main content

structured_asn1/memory/
work.rs

1//! Borrowed parser execution for transitive scratch work.
2
3use super::{HashWork, Memory, MemoryError, OwnedMap, OwnedVec, allocator};
4use crate::limits::BudgetView;
5
6mod private {
7    pub trait Sealed {}
8}
9
10/// An execution domain used by accounted collections and schema processing.
11///
12/// [`Memory`] owns an independent aggregate domain. [`Execution`] additionally
13/// borrows a parser's cumulative ceiling. The sealed implementations ensure
14/// that allocator and collection admission cannot bypass either boundary.
15pub trait Work: Clone + private::Sealed {
16    /// Storage ownership retained by the allocator independently of borrowed work.
17    type Retained: core::fmt::Debug;
18
19    /// Keeps an allocation's account alive even if its collection is forgotten.
20    /// Owned [`Memory`] already holds that account; borrowed [`Execution`]
21    /// supplies one owning handle per collection without allocating a new account.
22    fn retain_storage(&self) -> Self::Retained;
23
24    /// Moves the existing account owner when finishing independently owned storage.
25    /// The sealed implementations preserve the account without cloning it.
26    fn into_storage_owner(self, retained: Self::Retained) -> Memory;
27
28    /// Attaches current work while moving an existing same-account owner.
29    /// The caller verifies account identity before transferring storage.
30    fn with_storage_owner(&self, memory: Memory) -> (Self, Self::Retained);
31
32    /// The account retaining allocated capacity.
33    fn memory(&self) -> &Memory;
34
35    /// Admits work before execution, against every applicable ceiling.
36    fn charge_work(&self, amount: u64) -> Result<(), MemoryError>;
37
38    /// Creates empty accounted scratch retaining this execution boundary.
39    ///
40    /// No allocation occurs until a fallible growth operation. A collection
41    /// borrowing [`Execution`] cannot outlive that parser or execution account.
42    ///
43    /// # Examples
44    ///
45    /// ```
46    /// use std::sync::Arc;
47    /// use structured_asn1::{Parser, memory::{Memory, MemoryAccount, Work}};
48    /// let parser = Parser::ber(Memory::new(Arc::new(MemoryAccount::new(1024, 1024))));
49    /// let reader = parser.read(&[], 0).unwrap();
50    /// let mut scratch = reader.execution().vector();
51    /// scratch.try_push(42_u8)?;
52    /// assert_eq!(&*scratch, &[42]);
53    /// # Ok::<(), structured_asn1::memory::MemoryError>(())
54    /// ```
55    #[must_use]
56    fn vector<T>(&self) -> OwnedVec<T, Self> {
57        OwnedVec {
58            values: allocator_api2::vec::Vec::new_in(allocator::AccountAllocator::new(
59                self.clone(),
60            )),
61        }
62    }
63
64    /// Creates a bounded scratch map in this storage and execution domain.
65    /// Hash/probes, table growth and value movement retain the same ceilings.
66    #[must_use]
67    fn map<K: HashWork, V>(&self) -> OwnedMap<K, V, Self> {
68        OwnedMap::new(self.clone())
69    }
70}
71
72impl private::Sealed for Memory {}
73
74impl Work for Memory {
75    type Retained = ();
76
77    fn retain_storage(&self) {}
78
79    fn into_storage_owner(self, (): ()) -> Memory {
80        self
81    }
82
83    fn with_storage_owner(&self, memory: Memory) -> (Self, ()) {
84        (memory, ())
85    }
86
87    fn memory(&self) -> &Memory {
88        self
89    }
90
91    fn charge_work(&self, amount: u64) -> Result<(), MemoryError> {
92        Memory::charge_work(self, amount)
93    }
94}
95
96/// A borrowed aggregate account and original parser-session work ceiling.
97///
98/// Obtain this from [`crate::Reader::execution`]. Copies allocate nothing and
99/// acquire no shared ownership. Scratch storage and work use the reader's
100/// current account; rebinding the account never resets the parser ceiling.
101///
102/// A parser-bound allocation cannot survive its borrowed owner:
103///
104/// ```compile_fail
105/// use std::sync::Arc;
106/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount, Work}};
107/// let mut scratch;
108/// {
109///     let parser = Parser::ber(Memory::new(Arc::new(MemoryAccount::new(1024, 1024))));
110///     let reader = parser.read(&[], 0).unwrap();
111///     scratch = reader.execution().vector();
112///     scratch.try_push(42_u8).unwrap();
113/// }
114/// drop(scratch);
115/// ```
116#[derive(Debug, Clone, Copy)]
117pub struct Execution<'a> {
118    memory: &'a Memory,
119    budget: BudgetView<'a>,
120}
121
122impl<'a> Execution<'a> {
123    pub(crate) fn new(memory: &'a Memory, budget: BudgetView<'a>) -> Self {
124        Self { memory, budget }
125    }
126}
127
128impl private::Sealed for Execution<'_> {}
129
130impl Work for Execution<'_> {
131    type Retained = Memory;
132
133    fn retain_storage(&self) -> Memory {
134        self.memory.clone()
135    }
136
137    fn into_storage_owner(self, retained: Memory) -> Memory {
138        retained
139    }
140
141    fn with_storage_owner(&self, memory: Memory) -> (Self, Memory) {
142        (*self, memory)
143    }
144
145    fn memory(&self) -> &Memory {
146        self.memory
147    }
148
149    fn charge_work(&self, amount: u64) -> Result<(), MemoryError> {
150        self.budget
151            .work_in(amount, self.memory)
152            .map_err(|_| MemoryError::WorkExceeded)
153    }
154}