orx_imp_vec/imp_vec.rs
1use core::{cell::UnsafeCell, marker::PhantomData};
2use orx_pinned_vec::PinnedVec;
3use orx_split_vec::SplitVec;
4
5/// `ImpVec`, stands for immutable push vector 👿, is a data structure which allows appending elements with a shared reference.
6///
7/// Specifically, it extends vector capabilities with the following two methods:
8/// * `fn imp_push(&self, value: T)`
9/// * `fn imp_extend_from_slice(&self, slice: &[T])`
10///
11/// Note that both of these methods can be called with `&self` rather than `&mut self`.
12///
13/// # Motivation
14///
15/// Appending to a vector with a shared reference sounds unconventional, and it is.
16/// However, if we consider our vector as a bag of or a container of things rather than having a collective meaning;
17/// then, appending element or elements to the end of the vector:
18/// * does not mutate any of already added elements, and hence,
19/// * **it is not different than creating a new element in the scope**.
20///
21/// # Safety
22///
23/// It is natural to expect that appending elements to a vector does not affect already added elements.
24/// However, this is usually not the case due to underlying memory management.
25/// For instance, `std::vec::Vec` may move already added elements to different memory locations to maintain the contagious layout of the vector.
26///
27/// `PinnedVec` prevents such implicit changes in memory locations.
28/// It guarantees that push and extend methods keep memory locations of already added elements intact.
29/// Therefore, it is perfectly safe to hold on to references of the vector while appending elements.
30///
31/// Consider the classical example that does not compile, which is often presented to highlight the safety guarantees of rust:
32///
33/// ```rust
34/// let mut vec = vec![0, 1, 2, 3];
35///
36/// let ref_to_first = &vec[0];
37/// assert_eq!(ref_to_first, &0);
38///
39/// vec.push(4);
40///
41/// // does not compile due to the following reason: cannot borrow `vec` as mutable because it is also borrowed as immutable
42/// // assert_eq!(ref_to_first, &0);
43/// ```
44///
45/// This wonderful feature of the borrow checker of rust is not required and used for `imp_push` and `imp_extend_from_slice` methods of `ImpVec`
46/// since these methods do not require a `&mut self` reference.
47/// Therefore, the following code compiles and runs perfectly safely.
48///
49/// ```rust
50/// use orx_imp_vec::*;
51///
52/// let mut vec = ImpVec::new();
53/// vec.extend_from_slice(&[0, 1, 2, 3]);
54///
55/// let ref_to_first = &vec[0];
56/// assert_eq!(ref_to_first, &0);
57///
58/// vec.imp_push(4);
59/// assert_eq!(vec.len(), 5);
60///
61/// vec.imp_extend_from_slice(&[6, 7]);
62/// assert_eq!(vec.len(), 7);
63///
64/// assert_eq!(ref_to_first, &0);
65/// ```
66pub struct ImpVec<T, P = SplitVec<T>>
67where
68 P: PinnedVec<T>,
69{
70 pub(crate) pinned_vec: UnsafeCell<P>,
71 pub(crate) phantom: PhantomData<T>,
72}
73
74impl<T, P: PinnedVec<T>> ImpVec<T, P> {
75 /// Consumes the imp-vec into the wrapped inner pinned vector.
76 ///
77 /// # Example
78 ///
79 /// ```rust
80 /// use orx_split_vec::SplitVec;
81 /// use orx_imp_vec::ImpVec;
82 ///
83 /// let pinned_vec = SplitVec::new();
84 ///
85 /// let imp_vec = ImpVec::from(pinned_vec);
86 /// imp_vec.imp_push(42);
87 ///
88 /// let pinned_vec = imp_vec.into_inner();
89 /// assert_eq!(&pinned_vec, &[42]);
90 /// ```
91 pub fn into_inner(self) -> P {
92 self.pinned_vec.into_inner()
93 }
94
95 /// Pushes the `value` to the vector.
96 /// This method differs from the `push` method with the required reference.
97 /// Unlike `push`, `imp_push` allows to push the element with a shared reference.
98 ///
99 /// # Example
100 ///
101 /// ```rust
102 /// use orx_imp_vec::*;
103 ///
104 /// let mut vec = ImpVec::new();
105 ///
106 /// // regular push with &mut self
107 /// vec.push(42);
108 ///
109 /// // hold on to a reference to the first element
110 /// let ref_to_first = &vec[0];
111 /// assert_eq!(ref_to_first, &42);
112 ///
113 /// // imp_push with &self
114 /// vec.imp_push(7);
115 ///
116 /// // due to `PinnedVec` guarantees, this push will never invalidate prior references
117 /// assert_eq!(ref_to_first, &42);
118 /// ```
119 ///
120 /// # Safety
121 ///
122 /// Wrapping a `PinnedVec` with an `ImpVec` provides with two additional methods: `imp_push` and `imp_extend_from_slice`.
123 /// Note that these push and extend methods grow the vector by appending elements to the end.
124 ///
125 /// It is natural to expect that these operations do not change the memory locations of already added elements.
126 /// However, this is usually not the case due to underlying allocations.
127 /// For instance, `std::vec::Vec` may move already added elements in memory to maintain the contagious layout of the vector.
128 ///
129 /// `PinnedVec` prevents such implicit changes in memory locations.
130 /// It guarantees that push and extend methods keep memory locations of already added elements intact.
131 /// Therefore, it is perfectly safe to hold on to references of the vector while appending elements.
132 ///
133 /// Consider the classical example that does not compile, which is often presented to highlight the safety guarantees of rust:
134 ///
135 /// ```rust
136 /// let mut vec = vec![0, 1, 2, 3];
137 ///
138 /// let ref_to_first = &vec[0];
139 /// assert_eq!(ref_to_first, &0);
140 ///
141 /// vec.push(4);
142 ///
143 /// // does not compile due to the following reason: cannot borrow `vec` as mutable because it is also borrowed as immutable
144 /// // assert_eq!(ref_to_first, &0);
145 /// ```
146 ///
147 /// This wonderful feature of the borrow checker of rust is not required and used for `imp_push` and `imp_extend_from_slice` methods of `ImpVec`
148 /// since these methods do not require a `&mut self` reference.
149 /// Therefore, the following code compiles and runs perfectly safely.
150 ///
151 /// ```rust
152 /// use orx_imp_vec::*;
153 ///
154 /// let mut vec = ImpVec::new();
155 /// vec.extend_from_slice(&[0, 1, 2, 3]);
156 ///
157 /// let ref_to_first = &vec[0];
158 /// assert_eq!(ref_to_first, &0);
159 ///
160 /// vec.imp_push(4);
161 /// assert_eq!(vec.len(), 5);
162 ///
163 /// assert_eq!(ref_to_first, &0);
164 /// ```
165 ///
166 /// Although unconventional, this makes sense when we consider the `ImpVec` as a bag or container of things, rather than having a collective meaning.
167 /// In other words, when we do not rely on reduction methods, such as `count` or `sum`, appending element or elements to the end of the vector:
168 /// * does not mutate any of already added elements, and hence,
169 /// * **it is not different than creating a new element in the scope**.
170 pub fn imp_push(&self, value: T) {
171 self.pinned_mut().push(value);
172 }
173
174 /// Pushes the `value` to the vector and returns a reference to it.
175 ///
176 /// It is the composition of [`vec.imp_push(value)`] call followed by `&vec[vec.len() - 1]`.
177 ///
178 /// [`vec.imp_push(value)`]: crate::ImpVec::imp_push
179 ///
180 /// # Examples
181 ///
182 /// This method provides a shorthand for the following common use case.
183 ///
184 /// ```
185 /// use orx_imp_vec::*;
186 ///
187 /// let vec = ImpVec::new();
188 ///
189 /// vec.imp_push('a');
190 /// let a = &vec[vec.len() - 1];
191 /// assert_eq!(a, &'a');
192 ///
193 /// // or with imp_push_get_ref
194 ///
195 /// let b = vec.imp_push_get_ref('b');
196 /// assert_eq!(b, &'b');
197 /// ```
198 pub fn imp_push_get_ref(&self, value: T) -> &T {
199 let pinned = self.pinned_mut();
200 pinned.push(value);
201 &pinned[pinned.len() - 1]
202 }
203
204 /// Extends the vector with the given `slice`.
205 /// This method differs from the `extend_from_slice` method with the required reference.
206 /// Unlike `extend_from_slice`, `imp_extend_from_slice` allows to push the element with a shared reference.
207 ///
208 /// # Example
209 ///
210 /// ```rust
211 /// use orx_imp_vec::*;
212 ///
213 /// let mut vec = ImpVec::new();
214 ///
215 /// // regular extend_from_slice with &mut self
216 /// vec.extend_from_slice(&[42]);
217 ///
218 /// // hold on to a reference to the first element
219 /// let ref_to_first = &vec[0];
220 /// assert_eq!(ref_to_first, &42);
221 ///
222 /// // imp_extend_from_slice with &self
223 /// vec.imp_extend_from_slice(&[0, 1, 2, 3]);
224 /// assert_eq!(vec.len(), 5);
225 ///
226 /// // due to `PinnedVec` guarantees, this extend will never invalidate prior references
227 /// assert_eq!(ref_to_first, &42);
228 /// ```
229 ///
230 /// # Safety
231 ///
232 /// Wrapping a `PinnedVec` with an `ImpVec` provides with two additional methods: `imp_push` and `imp_extend_from_slice`.
233 /// Note that these push and extend methods grow the vector by appending elements to the end.
234 ///
235 /// It is natural to expect that these operations do not change the memory locations of already added elements.
236 /// However, this is usually not the case due to underlying allocations.
237 /// For instance, `std::vec::Vec` may move already added elements in memory to maintain the contagious layout of the vector.
238 ///
239 /// `PinnedVec` prevents such implicit changes in memory locations.
240 /// It guarantees that push and extend methods keep memory locations of already added elements intact.
241 /// Therefore, it is perfectly safe to hold on to references of the vector while appending elements.
242 ///
243 /// Consider the classical example that does not compile, which is often presented to highlight the safety guarantees of rust:
244 ///
245 /// ```rust
246 /// let mut vec = vec![0];
247 ///
248 /// let ref_to_first = &vec[0];
249 /// assert_eq!(ref_to_first, &0);
250 ///
251 /// vec.extend_from_slice(&[1, 2, 3, 4]);
252 ///
253 /// // does not compile due to the following reason: cannot borrow `vec` as mutable because it is also borrowed as immutable
254 /// // assert_eq!(ref_to_first, &0);
255 /// ```
256 ///
257 /// This wonderful feature of the borrow checker of rust is not required and used for `imp_push` and `imp_extend_from_slice` methods of `ImpVec`
258 /// since these methods do not require a `&mut self` reference.
259 /// Therefore, the following code compiles and runs perfectly safely.
260 ///
261 /// ```rust
262 /// use orx_imp_vec::*;
263 ///
264 /// let mut vec = ImpVec::new();
265 /// vec.push(0);
266 ///
267 /// let ref_to_first = &vec[0];
268 /// assert_eq!(ref_to_first, &0);
269 ///
270 /// vec.imp_extend_from_slice(&[1, 2, 3, 4]);
271 ///
272 /// assert_eq!(ref_to_first, &0);
273 /// ```
274 ///
275 /// Although unconventional, this makes sense when we consider the `ImpVec` as a bag or container of things, rather than having a collective meaning.
276 /// In other words, when we do not rely on reduction methods, such as `count` or `sum`, appending element or elements to the end of the vector:
277 /// * does not mutate any of already added elements, and hence,
278 /// * **it is not different than creating a new element in the scope**.
279 pub fn imp_extend_from_slice(&self, slice: &[T])
280 where
281 T: Clone,
282 {
283 self.pinned_mut().extend_from_slice(slice);
284 }
285
286 // helper
287 #[allow(clippy::mut_from_ref)]
288 pub(crate) fn pinned_mut(&self) -> &mut P {
289 // SAFETY: `ImpVec` does not implement Send or Sync.
290 // Further `imp_push` and `imp_extend_from_slice` methods are safe to call with a shared reference due to pinned vector guarantees.
291 // All other calls to this internal method require a mutable reference.
292 unsafe { &mut *self.pinned_vec.get() }
293 }
294
295 pub(crate) fn pinned(&self) -> &P {
296 // SAFETY: `ImpVec` does not implement Send or Sync.
297 // Further `imp_push` and `imp_extend_from_slice` methods are safe to call with a shared reference due to pinned vector guarantees.
298 // All other calls to this internal method require a mutable reference.
299 unsafe { &*self.pinned_vec.get() }
300 }
301}