Skip to main content

vyre_driver/
allocation.rs

1//! Backend-neutral fallible allocation reservation helpers.
2//!
3//! Concrete backends still own domain wording, but the arithmetic for
4//! "reserve additional" and "reserve up to target capacity" must not drift.
5
6use std::collections::{HashMap, HashSet, TryReserveError};
7use std::hash::{BuildHasher, Hash};
8
9use smallvec::{Array, SmallVec};
10
11use crate::BackendError;
12
13fn reserve_error(
14    context: &'static str,
15    requested: usize,
16    item: &'static str,
17    source: impl std::fmt::Display,
18    fix: &'static str,
19) -> BackendError {
20    BackendError::new(format!(
21        "{context} could not reserve {requested} {item}(s): {source}. Fix: {fix}."
22    ))
23}
24
25/// Reserve additional capacity for a [`Vec`] without changing its length.
26///
27/// # Errors
28///
29/// Returns [`BackendError`] when allocation fails.
30pub fn reserve_vec_additional<T>(
31    vec: &mut Vec<T>,
32    additional: usize,
33    context: &'static str,
34    item: &'static str,
35    fix: &'static str,
36) -> Result<(), BackendError> {
37    vec.try_reserve(additional)
38        .map_err(|source| reserve_error(context, additional, item, source, fix))
39}
40
41/// Ensure a [`Vec`] can hold `target_capacity` items without changing length,
42/// returning the standard allocation error for domain-specific adapters.
43///
44/// # Errors
45///
46/// Returns [`TryReserveError`] when allocation fails.
47pub fn try_reserve_vec_to_capacity<T>(
48    vec: &mut Vec<T>,
49    target_capacity: usize,
50) -> Result<(), TryReserveError> {
51    vyre_foundation::allocation::try_reserve_vec_to_capacity(vec, target_capacity)
52}
53
54/// Ensure a [`Vec`] can hold `target_capacity` items without changing length.
55///
56/// Uses `target_capacity - len`, not `target_capacity - capacity`, so a vector
57/// that was cleared after holding many elements still grows to the requested
58/// target if its retained capacity is too small.
59///
60/// # Errors
61///
62/// Returns [`BackendError`] when allocation fails.
63pub fn reserve_vec_to_capacity<T>(
64    vec: &mut Vec<T>,
65    target_capacity: usize,
66    context: &'static str,
67    item: &'static str,
68    fix: &'static str,
69) -> Result<(), BackendError> {
70    try_reserve_vec_to_capacity(vec, target_capacity)
71        .map_err(|source| reserve_error(context, target_capacity, item, source, fix))
72}
73
74/// Reserve additional capacity for a [`SmallVec`] without changing its length.
75///
76/// # Errors
77///
78/// Returns [`BackendError`] when allocation fails.
79pub fn reserve_smallvec_additional<A>(
80    vec: &mut SmallVec<A>,
81    additional: usize,
82    context: &'static str,
83    item: &'static str,
84    fix: &'static str,
85) -> Result<(), BackendError>
86where
87    A: Array,
88{
89    vec.try_reserve(additional)
90        .map_err(|source| reserve_error(context, additional, item, source, fix))
91}
92
93/// Ensure a [`SmallVec`] can hold `target_capacity` items without changing
94/// length.
95///
96/// # Errors
97///
98/// Returns [`BackendError`] when allocation fails.
99pub fn reserve_smallvec_to_capacity<A>(
100    vec: &mut SmallVec<A>,
101    target_capacity: usize,
102    context: &'static str,
103    item: &'static str,
104    fix: &'static str,
105) -> Result<(), BackendError>
106where
107    A: Array,
108{
109    vyre_foundation::allocation::try_reserve_smallvec_to_capacity(vec, target_capacity)
110        .map_err(|source| reserve_error(context, target_capacity, item, source, fix))
111}
112
113/// Ensure a [`HashMap`] can hold `target_capacity` entries without changing
114/// length, returning the standard allocation error for domain-specific
115/// adapters.
116///
117/// # Errors
118///
119/// Returns [`TryReserveError`] when allocation fails.
120pub fn try_reserve_hash_map_to_capacity<K, V, S>(
121    map: &mut HashMap<K, V, S>,
122    target_capacity: usize,
123) -> Result<(), TryReserveError>
124where
125    K: Eq + Hash,
126    S: BuildHasher,
127{
128    vyre_foundation::allocation::try_reserve_hash_map_to_capacity(map, target_capacity)
129}
130
131/// Ensure a [`HashSet`] can hold `target_capacity` entries without changing
132/// length, returning the standard allocation error for domain-specific
133/// adapters.
134///
135/// # Errors
136///
137/// Returns [`TryReserveError`] when allocation fails.
138pub fn try_reserve_hash_set_to_capacity<T, S>(
139    set: &mut HashSet<T, S>,
140    target_capacity: usize,
141) -> Result<(), TryReserveError>
142where
143    T: Eq + Hash,
144    S: BuildHasher,
145{
146    vyre_foundation::allocation::try_reserve_hash_set_to_capacity(set, target_capacity)
147}
148
149/// Ensure a [`HashMap`] can hold `target_capacity` entries without changing
150/// length.
151///
152/// # Errors
153///
154/// Returns [`BackendError`] when allocation fails.
155pub fn reserve_hash_map_to_capacity<K, V, S>(
156    map: &mut HashMap<K, V, S>,
157    target_capacity: usize,
158    context: &'static str,
159    item: &'static str,
160    fix: &'static str,
161) -> Result<(), BackendError>
162where
163    K: Eq + Hash,
164    S: BuildHasher,
165{
166    try_reserve_hash_map_to_capacity(map, target_capacity)
167        .map_err(|source| reserve_error(context, target_capacity, item, source, fix))
168}
169
170/// Ensure a [`HashSet`] can hold `target_capacity` entries without changing
171/// length.
172///
173/// # Errors
174///
175/// Returns [`BackendError`] when allocation fails.
176pub fn reserve_hash_set_to_capacity<T, S>(
177    set: &mut HashSet<T, S>,
178    target_capacity: usize,
179    context: &'static str,
180    item: &'static str,
181    fix: &'static str,
182) -> Result<(), BackendError>
183where
184    T: Eq + Hash,
185    S: BuildHasher,
186{
187    try_reserve_hash_set_to_capacity(set, target_capacity)
188        .map_err(|source| reserve_error(context, target_capacity, item, source, fix))
189}
190
191#[cfg(test)]
192mod tests {
193    use std::collections::{HashMap, HashSet};
194
195    use smallvec::SmallVec;
196
197    use super::{
198        reserve_hash_map_to_capacity, reserve_hash_set_to_capacity, reserve_smallvec_additional,
199        reserve_smallvec_to_capacity, reserve_vec_additional, reserve_vec_to_capacity,
200    };
201
202    #[test]
203    fn reserve_vec_to_capacity_grows_after_clear() {
204        let mut bytes = Vec::with_capacity(16);
205        bytes.extend_from_slice(&[1_u8; 12]);
206        bytes.clear();
207
208        reserve_vec_to_capacity(
209            &mut bytes,
210            20,
211            "generated reserve test",
212            "byte",
213            "split generated dispatch",
214        )
215        .expect("Fix: reserve_vec_to_capacity should grow cleared vectors");
216
217        assert!(bytes.capacity() >= 20);
218        assert!(bytes.is_empty());
219    }
220
221    #[test]
222    fn reserve_smallvec_to_capacity_grows_after_clear() {
223        let mut words = SmallVec::<[u32; 4]>::new();
224        words.extend_from_slice(&[1, 2, 3, 4]);
225        words.clear();
226
227        reserve_smallvec_to_capacity(
228            &mut words,
229            8,
230            "generated reserve test",
231            "word",
232            "split generated dispatch",
233        )
234        .expect("Fix: reserve_smallvec_to_capacity should grow cleared smallvecs");
235
236        assert!(words.capacity() >= 8);
237        assert!(words.is_empty());
238    }
239
240    #[test]
241    fn additional_reservations_preserve_length() {
242        let mut bytes = vec![1_u8, 2, 3];
243        reserve_vec_additional(
244            &mut bytes,
245            10,
246            "generated reserve test",
247            "byte",
248            "split generated dispatch",
249        )
250        .expect("Fix: reserve_vec_additional should not mutate length");
251        assert_eq!(bytes, vec![1, 2, 3]);
252
253        let mut small = SmallVec::<[u8; 2]>::new();
254        small.push(9);
255        reserve_smallvec_additional(
256            &mut small,
257            10,
258            "generated reserve test",
259            "byte",
260            "split generated dispatch",
261        )
262        .expect("Fix: reserve_smallvec_additional should not mutate length");
263        assert_eq!(small.as_slice(), &[9]);
264    }
265
266    #[test]
267    fn hash_collection_reservations_grow_after_clear_without_reinserting() {
268        let mut map = HashMap::<u32, u32>::with_capacity(4);
269        let mut set = HashSet::<u32>::with_capacity(4);
270        for value in 0..4 {
271            map.insert(value, value * 10);
272            set.insert(value);
273        }
274        map.clear();
275        set.clear();
276
277        for target in [8, 32, 128, 1024] {
278            reserve_hash_map_to_capacity(
279                &mut map,
280                target,
281                "generated reserve test",
282                "entry",
283                "split generated dispatch",
284            )
285            .expect("Fix: hash map target reservation should grow cleared maps");
286            reserve_hash_set_to_capacity(
287                &mut set,
288                target,
289                "generated reserve test",
290                "entry",
291                "split generated dispatch",
292            )
293            .expect("Fix: hash set target reservation should grow cleared sets");
294
295            assert!(map.capacity() >= target);
296            assert!(set.capacity() >= target);
297            assert!(map.is_empty());
298            assert!(set.is_empty());
299        }
300    }
301}